Re: [rust-dev] RFC: Opt-in builtin traits
On Sat, Mar 1, 2014 at 9:07 PM, Niko Matsakis n...@alum.mit.edu wrote: On Fri, Feb 28, 2014 at 11:23:23PM -0800, Kevin Ballard wrote: I'm also slightly concerned that #[deriving(Data)] gives the impression that there's a trait Data, so maybe that should be lowercased as in #[deriving(data)], or even just #[deriving(builtin)], but this is a lesser concern and somewhat bike-sheddy. This is definitely bikeshedding, but that's important too. While we're bikeshedding, I think we ought to rename the trait `Pod`, which doesn't fit into our verb scheme: Freeze Send Share Pod Yes please. It bothers me to no end when an acronym interacts with our camel case naming convention to form a word that's valid English and has nothing at all to do with the original meaning. (The other example is of course `Arc`.) I actually don't really mind if it's `Copy`, `POD`, `PlainOldData`... just not `Pod`. My first thought is to resurrect `Copy`, though of course it's very similar to `Clone`. Anyway, let's assume for now we rename `Pod` to `Copy`. In that case, I think I would have the following trait sets: data = Eq, Ord, Clone, Freeze, Hash, Share -- Note: not Copy! pod = data, Copy The idea is that almost all types (hashtables, etc) can use `data` (in particular, `data` is applicable to types with `~` pointers). Very simple types like `Point` can use `pod` (which is, after all, just plain old data). Niko ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev
Re: [rust-dev] RFC: Opt-in builtin traits
On Sat, Mar 1, 2014 at 8:24 PM, Niko Matsakis n...@alum.mit.edu wrote: On Fri, Feb 28, 2014 at 10:36:11PM +0100, Gábor Lehel wrote: I don't see the difference here. Why do you think this should be handled differently? To be honest I hadn't thought about it before. I agree there is no fundamental difference, though there may be a practical one. I have to mull this over. I think you're on to something in that there is a connection public/private fields, but I'm not quite sure what that ought to mean. I dislike the idea that adding a private field changes a whole bunch of defaults. (I've at times floating the idea of having a `struct`, where things are public-by-default and we auto-derive various traits, and a `class`, where things are private-by-default and everything is opt in, but nobody ever likes it but me. It's also unclear how to treat enums in such a scheme.) From this angle I can see more of its appeal... in fact I've been having vaguely similar ideas. This also ties in to the [nontrivial questions][1] surrounding how to remove `priv` while making things consistent and flexible and not requiring too many `pub`s. (In particular, I think it would be nice if structs and enums, resp. struct-like and tuple-like bodies for each, could be handled in a consistent fashion.) [1]: https://github.com/mozilla/rust/issues/8122#issuecomment-31233466 So I was thinking that for both structs and enums, you could have either all of their contents public or none. (With the potential exception of individually public struct fields.) It makes no sense to have an enum with some variants public and others private, nor is there much reason to support enums with public variants and private fields. The vast majority of the time, you want either a fully abstract datatype or a fully public one. (Even with structs, if you have a single private field, you lose the ability to construct and pattern match, and can only use dot syntax, so it's closer to a fully-private type than to a fully-public one... but the ability to control privacy per-field is kind of ingrained and the syntax is easy enough, so there seems to be no harm in keeping it.) Anyways, so that's similar to your idea. The hard part is coming up with syntax. I don't like the name `class`: it has too many connotations, and very few are ones we want. It *does* happen to have this meaning in C++, but I think that's sort of accidental, and tends to surprise even C++ users when they first learn of it (at least in my experience). In other languages with a struct-class duality, like C# and D, it carries a lot more baggage. I have a couple of ideas but am not truly satisfied with any of them. In all cases the default would be that everything is private, and the question is how to indicate otherwise: // this was pnkfelix's idea pub struct Point { pub: x: int, y: int } pub struct Point(pub: int, int) pub enum OptionT { pub: Some(T), None } // a minor variation putting the `pub` before the opening brace pub struct Point pub: { x: int, y: int } pub struct Point pub: (int, int) pub enum OptionT pub: { Some(T), None } // use `{ .. }` to indicate the contents are also public: pub { .. } struct Point { x: int, y: int } // (a bit awkward if the contents don't use braces!) pub { .. } struct Point(int, int) pub { .. } enum OptionT { Some(T), None } // a variation on the above pub { pub } struct Point { x: int, y: int } pub { pub } struct Point(int, int) pub { pub } enum OptionT { Some(T), None } My first thought regarding variance was that we ought to say that any type which is not freeze is invariant with respect to its parameters, but that is really quite overly conservative. That means for example that something like struct MyVecT { r: RcVecT } is not covariant with respect to `T`, which strikes me as quite unfortunate! Rc, after all, is not freeze, but it's not freeze because of the ref count, not the data it points at. ...I still think it would be best to rely on visibility, if we want to infer anything. (And for variance we probably do, otherwise almost everything will end up with overconservative defaults.) In terms of contracts, having public fields is a stronger contract than stating a variance for a type parameter. You're stating that the type will have *those exact fields*. Variance is merely a consequence. On the other hand, if a field is private, the intent is to make no contract at all, so nothing should be inferred from it. Some reasons I think it might be reasonable to treat variance differently: - Nobody but type theorists understands it, at least not at an intuitive level. It has been my experience that many, many very smart people just find it hard to grasp, so declaring variance explicitly is probably bad. Unfortunately, making all type parameters invariant is problematic when working with a type like `Option'a T`.
Re: [rust-dev] RFC: Opt-in builtin traits
On 01/03/14 07:23, Kevin Ballard wrote: I'm also slightly concerned that #[deriving(Data)] gives the impression that there's a trait Data, so maybe that should be lowercased as in #[deriving(data)], or even just #[deriving(builtin)], but this is a lesser concern and somewhat bike-sheddy. I guess that Data could actually be a declared as something like: trait Data : Eq + Clone + ... {} Gareth ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev
Re: [rust-dev] RFC: Opt-in builtin traits
On Fri, Feb 28, 2014 at 10:36:11PM +0100, Gábor Lehel wrote: I don't see the difference here. Why do you think this should be handled differently? To be honest I hadn't thought about it before. I agree there is no fundamental difference, though there may be a practical one. I have to mull this over. I think you're on to something in that there is a connection public/private fields, but I'm not quite sure what that ought to mean. I dislike the idea that adding a private field changes a whole bunch of defaults. (I've at times floating the idea of having a `struct`, where things are public-by-default and we auto-derive various traits, and a `class`, where things are private-by-default and everything is opt in, but nobody ever likes it but me. It's also unclear how to treat enums in such a scheme.) My first thought regarding variance was that we ought to say that any type which is not freeze is invariant with respect to its parameters, but that is really quite overly conservative. That means for example that something like struct MyVecT { r: RcVecT } is not covariant with respect to `T`, which strikes me as quite unfortunate! Rc, after all, is not freeze, but it's not freeze because of the ref count, not the data it points at. Some reasons I think it might be reasonable to treat variance differently: - Nobody but type theorists understands it, at least not at an intuitive level. It has been my experience that many, many very smart people just find it hard to grasp, so declaring variance explicitly is probably bad. Unfortunately, making all type parameters invariant is problematic when working with a type like `Option'a T`. - It's unclear if the variance of type parameters is likely to evolve over time. - It may not make much practical difference, since we don't have much subtyping in the language and it's likely to stay that way. I think right now subtyping is *only* induced via subtyping. The variable here is that if we introduced substruct types *and* subtyping rules there, subtyping might be more common. As an aside, I tried at one point to remove subtyping from the type inference altogether. This was working fine for a while but when I rebased the branch a while back I got lots of errors. I'm still tempted to try again. - On the other hand, some part of me thinks we ought to just remove the variance inference and instead have a simple variance scheme: no annotation means covariant, otherwise you write `mut T` or `mut 'a` to declare an invariant parameter (intution being: a parameter must be invariant if it used within an interior mutable thing like a `Cell`). That would rather make this question moot. Lots to think about! Niko ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev
Re: [rust-dev] RFC: Opt-in builtin traits
On Fri, Feb 28, 2014 at 11:23:23PM -0800, Kevin Ballard wrote: I'm also slightly concerned that #[deriving(Data)] gives the impression that there's a trait Data, so maybe that should be lowercased as in #[deriving(data)], or even just #[deriving(builtin)], but this is a lesser concern and somewhat bike-sheddy. This is definitely bikeshedding, but that's important too. While we're bikeshedding, I think we ought to rename the trait `Pod`, which doesn't fit into our verb scheme: Freeze Send Share Pod My first thought is to resurrect `Copy`, though of course it's very similar to `Clone`. Anyway, let's assume for now we rename `Pod` to `Copy`. In that case, I think I would have the following trait sets: data = Eq, Ord, Clone, Freeze, Hash, Share -- Note: not Copy! pod = data, Copy The idea is that almost all types (hashtables, etc) can use `data` (in particular, `data` is applicable to types with `~` pointers). Very simple types like `Point` can use `pod` (which is, after all, just plain old data). Niko ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev
Re: [rust-dev] RFC: Opt-in builtin traits
I like this 2014年3月2日 上午4:07于 Niko Matsakis n...@alum.mit.edu写道: On Fri, Feb 28, 2014 at 11:23:23PM -0800, Kevin Ballard wrote: I'm also slightly concerned that #[deriving(Data)] gives the impression that there's a trait Data, so maybe that should be lowercased as in #[deriving(data)], or even just #[deriving(builtin)], but this is a lesser concern and somewhat bike-sheddy. This is definitely bikeshedding, but that's important too. While we're bikeshedding, I think we ought to rename the trait `Pod`, which doesn't fit into our verb scheme: Freeze Send Share Pod My first thought is to resurrect `Copy`, though of course it's very similar to `Clone`. Anyway, let's assume for now we rename `Pod` to `Copy`. In that case, I think I would have the following trait sets: data = Eq, Ord, Clone, Freeze, Hash, Share -- Note: not Copy! pod = data, Copy The idea is that almost all types (hashtables, etc) can use `data` (in particular, `data` is applicable to types with `~` pointers). Very simple types like `Point` can use `pod` (which is, after all, just plain old data). Niko ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev
Re: [rust-dev] RFC: Opt-in builtin traits
I must admit I really like the *regularity* this brings to Rust. There is nothing more difficult to reason about that an irregular (even if reasonable) interface simply because one must keep all the rules in mind at any time (oh and sorry, there is a special condition described at page 364 that applies to this precise usecase even though the specs sounds like it's a universal rule). Certainly, the annotation could be a burden, but #[deriving(Data)] is extremely terse and brings in almost anything a user could need for its type in one shot. Finally, I believe the public API stability this brings is very necessary. Too often incidental properties are relied upon and broken during updates with the author not realizing it; when it's explicit, at least the library author makes a conscious choice. Maybe one way of preventing completely un-annotated pieces of data would be a lint that just checks that at least one property (Send, Freeze, ...) or a special annotation denoting their absence has been selected for each public-facing type. By having a #[deriving(...)] mandatory, it makes it easier for the lint pass to flag un-marked types without even having to reason whether or not the type would qualify. -- Matthieu On Fri, Feb 28, 2014 at 4:51 PM, Niko Matsakis n...@alum.mit.edu wrote: From http://smallcultfollowing.com/babysteps/blog/2014/02/28/rust-rfc-opt-in-builtin-traits/ : ## Rust RFC: opt-in builtin traits In today's Rust, there are a number of builtin traits (sometimes called kinds): `Send`, `Freeze`, `Share`, and `Pod` (in the future, perhaps `Sized`). These are expressed as traits, but they are quite unlike other traits in certain ways. One way is that they do not have any methods; instead, implementing a trait like `Freeze` indicates that the type has certain properties (defined below). The biggest difference, though, is that these traits are not implemented manually by users. Instead, the compiler decides automatically whether or not a type implements them based on the contents of the type. In this proposal, I argue to change this system and instead have users manually implement the builtin traits for new types that they define. Naturally there would be `#[deriving]` options as well for convenience. The compiler's rules (e.g., that a sendable value cannot reach a non-sendable value) would still be enforced, but at the point where a builtin trait is explicitly implemented, rather than being automatically deduced. There are a couple of reasons to make this change: 1. **Consistency.** All other traits are opt-in, including very common traits like `Eq` and `Clone`. It is somewhat surprising that the builtin traits act differently. 2. **API Stability.** The builtin traits that are implemented by a type are really part of its public API, but unlike other similar things they are not declared. This means that seemingly innocent changes to the definition of a type can easily break downstream users. For example, imagine a type that changes from POD to non-POD -- suddenly, all references to instances of that type go from copies to moves. Similarly, a type that goes from sendable to non-sendable can no longer be used as a message. By opting in to being POD (or sendable, etc), library authors make explicit what properties they expect to maintain, and which they do not. 3. **Pedagogy.** Many users find the distinction between pod types (which copy) and linear types (which move) to be surprising. Making pod-ness opt-in would help to ease this confusion. 4. **Safety and correctness.** In the presence of unsafe code, compiler inference is unsound, and it is unfortunate that users must remember to opt out from inapplicable kinds. There are also concerns about future compatibility. Even in safe code, it can also be useful to impose additional usage constriants beyond those strictly required for type soundness. I will first cover the existing builtin traits and define what they are used for. I will then explain each of the above reasons in more detail. Finally, I'll give some syntax examples. !-- more -- The builtin traits We currently define the following builtin traits: - `Send` -- a type that deeply owns all its contents. (Examples: `int`, `~int`, not `int`) - `Freeze` -- a type which is deeply immutable when accessed via an `T` reference. (Examples: `int`, `~int`, `int`, `mut int`, not `Cellint` or `Atomicint`) - `Pod` -- plain old data which can be safely copied via memcpy. (Examples: `int`, `int`, not `~int` or `mut int`) We are in the process of adding an additional trait: - `Share` -- a type which is threadsafe when accessed via an `T` reference. (Examples: `int`, `~int`, `int`, `mut int`, `Atomicint`, not `Cellint`) Proposed syntax Under this proposal, for a struct or enum to be considered send, freeze, pod, etc, those traits must be explicitly
Re: [rust-dev] RFC: Opt-in builtin traits
On Fri, Feb 28, 2014 at 09:38:18PM +0100, Matthieu Monrocq wrote: The main issue with the lint as you proposed is that if I *want* a linear type without any property, the lint will continuously bug me. Thus the idea of #[deriving(None)] (or whatever name) to shut the lint down, because once you start ignoring warnings, you miss the important ones. Lints can be disabled on a module-by-module basis. ``` #[allow(missing_traits)] ``` Niko ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev
Re: [rust-dev] RFC: Opt-in builtin traits
On Friday, February 28, 2014 at 11:15 AM, Matthieu Monrocq wrote: Maybe one way of preventing completely un-annotated pieces of data would be a lint that just checks that at least one property (Send, Freeze, ...) or a special annotation denoting their absence has been selected for each public-facing type. By having a #[deriving(...)] mandatory, it makes it easier for the lint pass to flag un-marked types without even having to reason whether or not the type would qualify. I generally like this idea; however, I find it a bit strange `deriving` would still be implemented as an attribute given its essential nature in the language. Haskell, of course, has `deriving` implemented as a first-class feature — might Rust be interested in something like that? Food for thought, at least. — John Grosen ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev
Re: [rust-dev] RFC: Opt-in builtin traits
(My idea for the lint was `#[allow_kind(Name)]` , which someone on IRC remarked as opt-out opt-in builtin traits On Fri, Feb 28, 2014 at 4:36 PM, Gábor Lehel glaebho...@gmail.com wrote: I think this is a really great idea. There's another potential compromise that would preserve most of its benefits, and reduce the annotation burden: There was another proposal earlier, driven by similar motivations, that structs with private fields should be non-`Pod`. Combining the two ideas, we could say that the built-in traits would be derived automatically for types with fully-public interiors, and would have to be declared or derived manually if any field is private. This would still accomplish what I think is the most important thing, which is to preserve abstraction boundaries: clients of a thing (type, module, package) should be insulated against changes to its private implementation. Relying on public information is, in this respect, however, fair game. The truly troublesome aspects of the current regime are, I believe, all consequences of the violation of abstraction boundaries. Types like `Cell` rely on these boundaries to ensure their safety, but under the current system, information about their private implementation leaks out. The OP and the above modification to it would both steer clear of this problem. I think the main tradeoffs between the two would be around simpler rules vs. fewer annotations, and the principle of least astonishment. This here idea is more complicated, because it has different rules for fully-public and abstract datatypes, and also (as currently) has different rules for built-in and user-defined traits. In exchange you only have to state your intentions explicitly if you have something to hide. The PoLA is harder to evaluate. Returning to the canonical example, if I write `struct Point { x: int, y: int }`, I think I'd be surprised if it weren't copyable. On the other hand, perhaps the afore-mentioned inconsistencies would also be surprising. So I dunno. This argument is rather weakened by the continued necessity of a `marker::InvariantType` marker. This could be read as an argument towards explicit variance. However, I think that in this particular case, the better solution is to introduce the `MutT` type described in #12577 -- the `MutT` type would give us the invariance. I don't see the difference here. Why do you think this should be handled differently? This is the same sort of abstraction boundary violation as the others: information about private fields is leaking out into the public interface via variance inference. Under the above scheme we could say that type parameters default to invariant for types with private fields, and are inferred for fully-public types. (How you would/could explicitly declare variance is another question, but kind of orthogonal to the idea that you /should/.) On Fri, Feb 28, 2014 at 4:51 PM, Niko Matsakis n...@alum.mit.edu wrote: From http://smallcultfollowing.com/babysteps/blog/2014/02/28/rust-rfc-opt-in-builtin-traits/: ## Rust RFC: opt-in builtin traits In today's Rust, there are a number of builtin traits (sometimes called kinds): `Send`, `Freeze`, `Share`, and `Pod` (in the future, perhaps `Sized`). These are expressed as traits, but they are quite unlike other traits in certain ways. One way is that they do not have any methods; instead, implementing a trait like `Freeze` indicates that the type has certain properties (defined below). The biggest difference, though, is that these traits are not implemented manually by users. Instead, the compiler decides automatically whether or not a type implements them based on the contents of the type. In this proposal, I argue to change this system and instead have users manually implement the builtin traits for new types that they define. Naturally there would be `#[deriving]` options as well for convenience. The compiler's rules (e.g., that a sendable value cannot reach a non-sendable value) would still be enforced, but at the point where a builtin trait is explicitly implemented, rather than being automatically deduced. There are a couple of reasons to make this change: 1. **Consistency.** All other traits are opt-in, including very common traits like `Eq` and `Clone`. It is somewhat surprising that the builtin traits act differently. 2. **API Stability.** The builtin traits that are implemented by a type are really part of its public API, but unlike other similar things they are not declared. This means that seemingly innocent changes to the definition of a type can easily break downstream users. For example, imagine a type that changes from POD to non-POD -- suddenly, all references to instances of that type go from copies to moves. Similarly, a type that goes from sendable to non-sendable can no longer be used as a message. By opting in to being POD (or sendable, etc), library
Re: [rust-dev] RFC: Opt-in builtin traits
On Feb 28, 2014, at 8:10 PM, Kang Seonghoon some...@mearie.org wrote: 2014-03-01 6:24 GMT+09:00 John Grosen jmgro...@gmail.com: On Friday, February 28, 2014 at 11:15 AM, Matthieu Monrocq wrote: Maybe one way of preventing completely un-annotated pieces of data would be a lint that just checks that at least one property (Send, Freeze, ...) or a special annotation denoting their absence has been selected for each public-facing type. By having a #[deriving(...)] mandatory, it makes it easier for the lint pass to flag un-marked types without even having to reason whether or not the type would qualify. I generally like this idea; however, I find it a bit strange `deriving` would still be implemented as an attribute given its essential nature in the language. Haskell, of course, has `deriving` implemented as a first-class feature — might Rust be interested in something like that? Food for thought, at least. I second to this. Indeed, we already have similar concerns about externally-implemented `#[deriving]` (#11813, and somewhat tangently, #11298), as syntax extensions don't have any clue about paths. I actually rather like the fact that deriving is implemented as an attribute, because it's one less bit of syntax. Right now it's still implemented in the compiler, but this could theoretically eventually move into libstd entirely as a #[macro_registrar]. My main concern with this proposal overall is that types will forget to derive things. I know I almost always forget to derive Eq and Clone for my own structs until I run into an error due to their lack. A lint to warn about missing derivations would mitigate this a lot, although I'm worried that if someone opts out of a single trait by using #[allow(missing_traits)] and a new trait is added to the set, the author will never realize they're missing the new trait. I'm also concerned that if you need to opt out of a single trait from #[deriving(Data)] then you can't use #[deriving(Data)] and must instead list all of the remaining traits. Perhaps for both problems we could introduce the idea of #[deriving(!Send)], which would let me say #[deriving(Data,!Send,!Freeze)] to opt out of those two. I'm also slightly concerned that #[deriving(Data)] gives the impression that there's a trait Data, so maybe that should be lowercased as in #[deriving(data)], or even just #[deriving(builtin)], but this is a lesser concern and somewhat bike-sheddy. -Kevin ___ Rust-dev mailing list Rust-dev@mozilla.org https://mail.mozilla.org/listinfo/rust-dev