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
