Kinds are assigned to generic type parameters to determine what a
generic function can do with values of that kind.

Rust currently has three kinds:
 - 'unique' types can be moved and sent (all the scalar types, unique
boxes with no resources in them)
 - 'shared' types can be moved, but can not be sent (anything
involving refcounted boxes-- @, obj, lambda)
 - 'pinned' types can be sent, but not moved (this currently applies
to resources, anything including resources, and blocks)

I'm arguing that this is not a very good system, and that we'd be
better off considering 'copying' rather than 'moving' as a property.
This was the way the system was originally designed, but when
implementing it, Graydon changed to the system outlined above. In
private mail, he told me that the reason for this was that we couldn't
allow blocks (functions closing over local frames) to be moved.

I propose the following alternative, which allows resources to be
moved again (*), greatly reduces the awkwardness of declaring types
for generic functions, and defines a more specialized approach to
safety for blocks (**), and actually help define noncopyability (which
the current system doesn't do at all).

We'd have the following kinds (better terms will have to be found, but
I'm using these for clarity):
- 'send,copy' types can be copied and sent (scalar types, unique boxes
without resources in them)
- 'nosend,copy' types can be copied, not sent (the same as the
'shared' types above, refcounted things)
- 'nosend,nocopy' types can't be sent or copied (resources, unique
boxes with resources in them, blocks)

Contrary to the old kinds, this forms a hierarchy, and the kinds lower
in the list can be treated as sub-kinds of those above them (a
'send,copy' value can be safely treated as a 'nosend,nocopy' value).
Most generic functions will neither send nor copy values of their
parameterized type, so they can safely default to 'nosend,nocopy'
without losing genericity. When they *do* copy or send, and the
programmer forgets to annotate the type parameter as such, a clear,
understandable error message can be provided.

Generic types (type<T> and tag<T>) do not seem to have a reason to
ever narrow their kind bounds in this system, so (unless I am missing
something), they should probably not even be allowed to specify a kind
on their parameters.

We'll have to define more closely when copying happens. I probably
forgot some cases here, but—an lvalue (rvalues are always conceptually
moved) is copied when:
- It is assigned to some other lvalue with =
- It is copied into a lambda closure
- It is passed as an argument in an unsafe way (safe argument passing
does not copy)
- It is used as the content of a newly created data structure (it is
not yet clear how tag/resource constructors can be distinguished from
other functions for this purpose. maybe they should take their args in
move mode, though that would require one to always say
option::some(copy localvar), which is also awkward)
- It is returned from a function or block

It might be useful to consider the last use of a local (let) variable
to be an rvalue, since that local will never be referenced again, and
it is always safe to move out of it. This will cause most returns to
become moves (though returning a non-move-mode argument or the content
of a data structure is still a copy). The intention is to only
annotate things as a copy where there will *actually* be two reachable
versions of a value after the operation. (The translator pass is
already optimizing most of the situations where this isn't the case
into a move or construct-in-place. This system could move some of that
logic into the kind-checking pass.)

Block safety will have to be handled on a more or less syntactic
level. Values with block type can be restricted to only appear in
function argument or callee position, and when appearing in argument
position, the argument mode has to be by-reference. This is a kludge,
but a relatively simple one (a small pass coming after typechecking
can verify it).

Given this, we can have a kind-checking pass that actually works,
without imposing a big burden on users. Generic functions (and
objects, as they currently exist) will have their parameters be
'nocopy,nosend' when no kind annotation is present, which will usually
be the right thing. Generic data types will never have to be annotated
with kinds.

I hope people agree this system is easier to understand than the
current one. I also hope I didn't miss any gaping soundness holes.
Please comment.

*) Resources that can't be moved are rather useless. They can only
exist as local variables and never be stored in a data structure. This
is why there is currently a kludge in the kind-checking code to allow
resources to be constructed into data structures. This is needlessly
special-cased, and doesn't really work very well at the moment (you
can't, for example, put a resource in a tag value.)

**) If the 'regions' design that Patrick and Niko are working on pads
out, that will produce a much better way of handling blocks.

Best,
Marijn
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev

Reply via email to