Hi everyone,
Dave and I just whiteboarded ideas for function types, in light of the
issues that Andrew was seeing. We came to these tentative conclusions:
* Functions being noncopyable doesn't seem to work, because it breaks
"bind". "bind" must copy the environment of the function bound to.
* Blocks (unnamed lambdas) and named functions have different
properties. The former can capture stack locals, while the latter has
dynamic lifetime and can capture only shared state. Also, blocks have
extra control flow options (break, continue, outer return) while named
functions don't have this ability.
* Interior semantics aren't enough to guarantee that unnamed lambdas
can't escape their defined scope. It'd be possible to swap an interior
"fn" with another interior "fn" (in some data structure, say) and have
it escape its scope.
* We would like to be able to declare functions that take as one (or
more) of their arguments either a named function or a block, without
having to duplicate the function. For example, we should be able to say
map([ 1, 2, 3 ], { |x| x * x }) or map([ 1, 2, 3 ], square).
To deal with these issues, here's a conservative proposal as a starting
point:
(1) The general "fn" type represents a block. It can only appear as the
type of an alias parameter to a function.
(2) All named functions have type "@fn". They are reference counted.
They can close over outer variables, but only those on the heap (i.e.
only over boxes).
(3) As a corollary to point (1), blocks can only be constructed with the
{ |x| ... } syntax when calling functions. They are able to use "break",
"continue", and "return" with the semantics that we discussed previously.
(4) "bind" stays around, but only works on values of type @fn.
(5) Named functions (@fn) can be converted to type "fn" when calling a
function using the dereference operator "*". In the example above, we
could call "map" using map([ 1, 2, 3 ], *square). Under the hood, an
extra function will be generated; this is required because blocks need a
different signature than named functions in order to handle "break" and
"return".
Drawbacks:
* "fn" and "@fn" are now different types; there is no compositionality,
although the dereference operator lessens the pain of this somewhat.
* Functions can't be sent over channels.
We could potentially extend this later to remove some of the
limitations, but I think this works as a starting point. Andrew, I'm
particularly curious as to whether this helps solve some of the issues
you were encountering. To the rest of the team, I'm interested to hear
your feedback.
Patrick
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev