[racket] TR: struct: #:prefab option

From: Asumu Takikawa (asumu at ccs.neu.edu)
Date: Mon May 6 13:39:40 EDT 2013

On 2013-05-06 08:46:04 -0700, Eric Dobson wrote:
> Couldn't this be solved by protecting the struct on export?

I'm not sure this is sufficient. Consider the following code:

  #lang racket/load

  (module foo racket
    (struct foo ([f #:mutable]) #:prefab)
    (define y (foo values))
    (define (mutater) (set-foo-f! y (λ (x) (string->number x))))
    (provide y mutater))

  (module bar typed/racket
    (struct: foo ([f : (Float -> Float)]) #:mutable #:prefab)
    (require/typed 'foo
                   [y foo]
                   [mutater (-> Void)])
    (mutater)
    ((foo-f y) 5.3))

The use of mutation lets you sneak values that don't match the type.
Even if you require the struct instance with `struct/dc` and apply an
impersonator-based contract, you can't prevent the un-impersonated
access on the `foo` side.

This actually looks like it's a problem even without #:prefab structs:

  #lang racket/load

  (module foo racket
    (struct foo ([f #:mutable]))
    (define y (foo values))
    (define (mutater) (set-foo-f! y (λ (x) (string->number x))))
    (provide y (struct-out foo) mutater))

  (module bar typed/racket
    (require/typed 'foo
                   [#:struct foo ([f : (Float -> Float)])]
                   [y foo]
                   [mutater (-> Void)])
    (mutater)
    ((foo-f y) 5.3))

Another thing to be careful of is struct inheritance. For example:

  #lang racket/load

  (module foo typed/racket
    (struct: foo ([f : (Float -> Float)]) #:prefab)
    (struct: foo2 foo ([g : (Float -> Float)]) #:prefab)
    (define x (ann (foo2 (λ (x) (* x 5.3))
                         (λ (x) (* x 5.3)))
                   foo))
    ;; Note that this is a type error
    ;(foo2-g x)
    (provide x))

  (module bar racket
    (require 'foo)
    (struct foo (f) #:prefab)
    (struct foo2 foo (g) #:prefab)
    ((foo2-g x) "foo"))

In this case, `foo2` inherits from `foo` and so you can upcast an
instance of `foo2` to `foo`. But then you forget a field, and if the
type->contract translation isn't smart, then you may fail to protect all
the fields (that an untyped client can now guess and misuse).

Cheers,
Asumu

Posted on the users mailing list.