RFC: Opt-in builtin traits

classic Classic list List threaded Threaded
16 messages Options
Reply | Threaded
Open this post in threaded view
|

RFC: Opt-in builtin traits

Niko Matsakis
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 `Cell<int>` or
   `Atomic<int>`)
- `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`,
  `Atomic<int>`, not `Cell<int>`)

#### Proposed syntax

Under this proposal, for a struct or enum to be considered send,
freeze, pod, etc, those traits must be explicitly implemented:

    struct Foo { ... }
    impl Send for Foo { }
    impl Freeze for Foo { }
    impl Pod for Foo { }
    impl Share for Foo { }

For generic types, a conditional impl would be more appropriate:

    enum Option<T> { Some(T), None }
    impl<T:Send> Send for Option<T> { }
    // etc
   
As usual, deriving forms would be available that would expand into
impls like the one shown above.

Whenever a builtin trait is implemented, the compiler will enforce the
same requirements it enforces today. Therefore, code like the
following would yield an error:

    struct Foo<'a> { x: &'a int }
   
    // ERROR: Cannot implement `Send` because the field `x` has type
    // `&'a int` which is not sendable.
    impl<'a> Send for Foo<'a> { }

These impls would follow the usual coherence requirements. For
example, a struct can only be declared as `Share` within the crate
where it is defined.

For convenience, I also propose a deriving shorthand
`#[deriving(Data)]` that would implement a "package" of common traits
for types that contain simple data: `Eq`, `Ord`, `Clone`, `Show`,
`Send`, `Share`, `Freeze`, and `Pod`.

#### Pod and linearity

One of the most important aspects of this proposal is that the `Pod`
trait would be something that one "opts in" to. This means that
structs and enums would *move by default* unless their type is
explicitly declared to be `Pod`. So, for example, the following
code would be in error:

    struct Point { x: int, y: int }
    ...
    let p = Point { x: 1, y: 2 };
    let q = p;  // moves p
    print(p.x); // ERROR
   
To allow that example, one would have to impl `Pod` for `Point`:

    struct Point { x: int, y: int }
    impl Pod for Point { }
    ...
    let p = Point { x: 1, y: 2 };
    let q = p;  // copies p, because Point is Pod
    print(p.x); // OK
   
Effectively this change introduces a three step ladder for types:

1. If you do nothing, your type is *linear*, meaning that it moves
   from place to place and can never be copied in any way. (We need a
   better name for that.)
2. If you implement `Clone`, your type is *cloneable*, meaning that it
   moves from place to place, but it can be explicitly cloned. This is
   suitable for cases where copying is expensive.
3. If you implement `Pod`, your type is *plain old data*, meaning that
   it is just copied by default without the need for an explicit
   clone.  This is suitable for small bits of data like ints or
   points.
   
What is nice about this change is that when a type is defined, the
user makes an *explicit choice* between these three options.

#### Consistency

This change would bring the builtin traits more in line with other
common traits, such as `Eq` and `Clone`. On a historical note, this
proposal continues a trend, in that both of those operations used to
be natively implemented by the compiler as well.

#### API Stability

The set of builtin traits implemented by a type must be considered
part of its public inferface. At present, though, it's quite invisible
and not under user control. If a type is changed from `Pod` to
non-pod, or `Send` to non-send, no error message will result until
client code attempts to use an instance of that type. In general we
have tried to avoid this sort of situation, and instead have each
declaration contain enough information to check it indepenently of its
uses. Issue #12202 describes this same concern, specifically with
respect to stability attributes.

Making opt-in explicit effectively solves this problem. It is clearly
written out which traits a type is expected to fulfill, and if the
type is changed in such a way as to violate one of these traits, an
error will be reported at the `impl` site (or `#[deriving]`
declaration).

#### Pedagogy

When users first start with Rust, ownership and ownership transfer is
one of the first things that they must learn. This is made more
confusing by the fact that types are automatically divided into pod
and non-pod without any sort of declaration. It is not necessarily
obvious why a `T` and `~T` value, which are *semantically equivalent*,
behave so differently by default. Makes the pod category something you
opt into means that types will all be linear by default, which can
make teaching and leaning easier.

#### Safety and correctness: unsafe code

For safe code, the compiler's rules for deciding whether or not a type
is sendable (and so forth) are perfectly sound. However, when unsafe
code is involved, the compiler may draw the wrong conclusion. For such
cases, types must *opt out* of the builtin traits.

In general, the *opt out* approach seems to be hard to reason about:
many people (including myself) find it easier to think about what
properties a type *has* than what properties it *does not* have,
though clearly the two are logically equivalent in this binary world
we programmer's inhabit.

More concretely, opt out is dangerous because it means that types with
unsafe methods are generally *wrong by default*. As an example,
consider the definition of the `Cell` type:

    struct Cell<T> {
        priv value: T
    }
   
This is a perfectly ordinary struct, and hence the compiler would
conclude that cells are freezable (if `T` is freezable) and so forth.
However, the *methods* attached to `Cell` use unsafe magic to mutate
`value`, even when the `Cell` is aliased:

    impl<T:Pod> Cell<T> {
        pub fn set(&self, value: T) {
            unsafe {
                *cast::transmute_mut(&self.value) = value
            }
        }
    }

To accommodate this, we currently use *marker types* -- special types
known to the compiler which are considered nonpod and so forth. Therefore,
the full definition of `Cell` is in fact:

    pub struct Cell<T> {
        priv value: T,
        priv marker1: marker::InvariantType<T>,
        priv marker2: marker::NoFreeze,
    }

Note the two markers. The first, `marker1`, is a hint to the variance
engine indicating that the type `Cell` must be
[invariant with respect to its type argument][inv]. The second,
`marker2`, indicates that `Cell` is non-freeze. This then informs the
compiler that the referent of a `&Cell<T>` can't be considered
immutable. The problem here is that, if you don't know to opt-out,
you'll wind up with a type definition that is unsafe.

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 `Mut<T>` type described
in #12577 -- the `Mut<T>` type would give us the invariance.

Using `Mut<T>` brings us back to a world where any type that uses
`Mut<T>` to obtain interior mutability is correct by default, at least
with respect to the builtin kinds. Types like `Atomic<T>` and
`Volatile<T>`, which guarantee data race freedom, would therefore have
to *opt in* to the `Share` kind, and types like `Cell<T>` would simply
do nothing.

#### Safety and correctness: future compatibility

Another concern about having the compiler automatically infer
membership into builtin bounds is that we may find cause to add new
bounds in the future. In that case, existing Rust code which uses
unsafe methods might be inferred incorrectly, because it would not
know to opt out of those future bounds. Therefore, any future bounds
will *have* to be opt out anyway, so perhaps it is best to be
consistent from the start.

#### Safety and correctness: semantic constraints

Even if type safety is maintained, some types ought not to be copied
for semantic reasons. An example from the compiler is the
`Datum<Rvalue>` type, which is used in code generation to represent
the computed result of an rvalue expression. At present, the type
`Rvalue` implements a (empty) destructor -- the sole purpose of this
destructor is to ensure that datums are not consumed more than once,
because this would likely correspond to a code gen bug, as it would
mean that the result of the expression evaluation is consumed more
than once. Another example might be a newtype'd integer used for
indexing into a thread-local array: such a value ought not to be
sendable. And so forth. Using marker types for these kinds of
situations, or empty destructors, is very awkward. Under this
proposal, users needs merely refrain from implementing the relevant
traits.

#### The `Sized` bound

In DST, we plan to add a `Sized` bound. I do not feel like users
should manually implemented `Sized`. It seems tedious and rather
ludicrous.

#### Counterarguments

The downsides of this proposal are:

- There is some annotation burden. I had intended to gather statistics
  to try and measure this but have not had the time.
 
- If a library forgets to implement all the relevant traits for a
  type, there is little recourse for users of that library beyond pull
  requests to the original repository. This is already true with
  traits like `Eq` and `Ord`. However, as SiegeLord noted on IRC, that
  you can often work around the absence of `Eq` with a newtype
  wrapper, but this is not true if a type fails to implement `Send` or
  `Pod`. This danger (forgetting to implement traits) is essentially
  the counterbalance to the "forward compatbility" case made above:
  where implementing traits by default means types may implement too
  much, forcing explicit opt in means types may implement too little.
  One way to mitigate this problem would be to have a lint for when an
  impl of some kind (etc) would be legal, but isn't implemented, at
  least for publicly exported types in library crates.

_______________________________________________
Rust-dev mailing list
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev
Reply | Threaded
Open this post in threaded view
|

Re: RFC: Opt-in builtin traits

Matthieu Monrocq
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 <[hidden email]> 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 `Cell<int>` or
   `Atomic<int>`)
- `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`,
  `Atomic<int>`, not `Cell<int>`)

#### Proposed syntax

Under this proposal, for a struct or enum to be considered send,
freeze, pod, etc, those traits must be explicitly implemented:

    struct Foo { ... }
    impl Send for Foo { }
    impl Freeze for Foo { }
    impl Pod for Foo { }
    impl Share for Foo { }

For generic types, a conditional impl would be more appropriate:

    enum Option<T> { Some(T), None }
    impl<T:Send> Send for Option<T> { }
    // etc

As usual, deriving forms would be available that would expand into
impls like the one shown above.

Whenever a builtin trait is implemented, the compiler will enforce the
same requirements it enforces today. Therefore, code like the
following would yield an error:

    struct Foo<'a> { x: &'a int }

    // ERROR: Cannot implement `Send` because the field `x` has type
    // `&'a int` which is not sendable.
    impl<'a> Send for Foo<'a> { }

These impls would follow the usual coherence requirements. For
example, a struct can only be declared as `Share` within the crate
where it is defined.

For convenience, I also propose a deriving shorthand
`#[deriving(Data)]` that would implement a "package" of common traits
for types that contain simple data: `Eq`, `Ord`, `Clone`, `Show`,
`Send`, `Share`, `Freeze`, and `Pod`.

#### Pod and linearity

One of the most important aspects of this proposal is that the `Pod`
trait would be something that one "opts in" to. This means that
structs and enums would *move by default* unless their type is
explicitly declared to be `Pod`. So, for example, the following
code would be in error:

    struct Point { x: int, y: int }
    ...
    let p = Point { x: 1, y: 2 };
    let q = p;  // moves p
    print(p.x); // ERROR

To allow that example, one would have to impl `Pod` for `Point`:

    struct Point { x: int, y: int }
    impl Pod for Point { }
    ...
    let p = Point { x: 1, y: 2 };
    let q = p;  // copies p, because Point is Pod
    print(p.x); // OK

Effectively this change introduces a three step ladder for types:

1. If you do nothing, your type is *linear*, meaning that it moves
   from place to place and can never be copied in any way. (We need a
   better name for that.)
2. If you implement `Clone`, your type is *cloneable*, meaning that it
   moves from place to place, but it can be explicitly cloned. This is
   suitable for cases where copying is expensive.
3. If you implement `Pod`, your type is *plain old data*, meaning that
   it is just copied by default without the need for an explicit
   clone.  This is suitable for small bits of data like ints or
   points.

What is nice about this change is that when a type is defined, the
user makes an *explicit choice* between these three options.

#### Consistency

This change would bring the builtin traits more in line with other
common traits, such as `Eq` and `Clone`. On a historical note, this
proposal continues a trend, in that both of those operations used to
be natively implemented by the compiler as well.

#### API Stability

The set of builtin traits implemented by a type must be considered
part of its public inferface. At present, though, it's quite invisible
and not under user control. If a type is changed from `Pod` to
non-pod, or `Send` to non-send, no error message will result until
client code attempts to use an instance of that type. In general we
have tried to avoid this sort of situation, and instead have each
declaration contain enough information to check it indepenently of its
uses. Issue #12202 describes this same concern, specifically with
respect to stability attributes.

Making opt-in explicit effectively solves this problem. It is clearly
written out which traits a type is expected to fulfill, and if the
type is changed in such a way as to violate one of these traits, an
error will be reported at the `impl` site (or `#[deriving]`
declaration).

#### Pedagogy

When users first start with Rust, ownership and ownership transfer is
one of the first things that they must learn. This is made more
confusing by the fact that types are automatically divided into pod
and non-pod without any sort of declaration. It is not necessarily
obvious why a `T` and `~T` value, which are *semantically equivalent*,
behave so differently by default. Makes the pod category something you
opt into means that types will all be linear by default, which can
make teaching and leaning easier.

#### Safety and correctness: unsafe code

For safe code, the compiler's rules for deciding whether or not a type
is sendable (and so forth) are perfectly sound. However, when unsafe
code is involved, the compiler may draw the wrong conclusion. For such
cases, types must *opt out* of the builtin traits.

In general, the *opt out* approach seems to be hard to reason about:
many people (including myself) find it easier to think about what
properties a type *has* than what properties it *does not* have,
though clearly the two are logically equivalent in this binary world
we programmer's inhabit.

More concretely, opt out is dangerous because it means that types with
unsafe methods are generally *wrong by default*. As an example,
consider the definition of the `Cell` type:

    struct Cell<T> {
        priv value: T
    }

This is a perfectly ordinary struct, and hence the compiler would
conclude that cells are freezable (if `T` is freezable) and so forth.
However, the *methods* attached to `Cell` use unsafe magic to mutate
`value`, even when the `Cell` is aliased:

    impl<T:Pod> Cell<T> {
        pub fn set(&self, value: T) {
            unsafe {
                *cast::transmute_mut(&self.value) = value
            }
        }
    }

To accommodate this, we currently use *marker types* -- special types
known to the compiler which are considered nonpod and so forth. Therefore,
the full definition of `Cell` is in fact:

    pub struct Cell<T> {
        priv value: T,
        priv marker1: marker::InvariantType<T>,
        priv marker2: marker::NoFreeze,
    }

Note the two markers. The first, `marker1`, is a hint to the variance
engine indicating that the type `Cell` must be
[invariant with respect to its type argument][inv]. The second,
`marker2`, indicates that `Cell` is non-freeze. This then informs the
compiler that the referent of a `&Cell<T>` can't be considered
immutable. The problem here is that, if you don't know to opt-out,
you'll wind up with a type definition that is unsafe.

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 `Mut<T>` type described
in #12577 -- the `Mut<T>` type would give us the invariance.

Using `Mut<T>` brings us back to a world where any type that uses
`Mut<T>` to obtain interior mutability is correct by default, at least
with respect to the builtin kinds. Types like `Atomic<T>` and
`Volatile<T>`, which guarantee data race freedom, would therefore have
to *opt in* to the `Share` kind, and types like `Cell<T>` would simply
do nothing.

#### Safety and correctness: future compatibility

Another concern about having the compiler automatically infer
membership into builtin bounds is that we may find cause to add new
bounds in the future. In that case, existing Rust code which uses
unsafe methods might be inferred incorrectly, because it would not
know to opt out of those future bounds. Therefore, any future bounds
will *have* to be opt out anyway, so perhaps it is best to be
consistent from the start.

#### Safety and correctness: semantic constraints

Even if type safety is maintained, some types ought not to be copied
for semantic reasons. An example from the compiler is the
`Datum<Rvalue>` type, which is used in code generation to represent
the computed result of an rvalue expression. At present, the type
`Rvalue` implements a (empty) destructor -- the sole purpose of this
destructor is to ensure that datums are not consumed more than once,
because this would likely correspond to a code gen bug, as it would
mean that the result of the expression evaluation is consumed more
than once. Another example might be a newtype'd integer used for
indexing into a thread-local array: such a value ought not to be
sendable. And so forth. Using marker types for these kinds of
situations, or empty destructors, is very awkward. Under this
proposal, users needs merely refrain from implementing the relevant
traits.

#### The `Sized` bound

In DST, we plan to add a `Sized` bound. I do not feel like users
should manually implemented `Sized`. It seems tedious and rather
ludicrous.

#### Counterarguments

The downsides of this proposal are:

- There is some annotation burden. I had intended to gather statistics
  to try and measure this but have not had the time.

- If a library forgets to implement all the relevant traits for a
  type, there is little recourse for users of that library beyond pull
  requests to the original repository. This is already true with
  traits like `Eq` and `Ord`. However, as SiegeLord noted on IRC, that
  you can often work around the absence of `Eq` with a newtype
  wrapper, but this is not true if a type fails to implement `Send` or
  `Pod`. This danger (forgetting to implement traits) is essentially
  the counterbalance to the "forward compatbility" case made above:
  where implementing traits by default means types may implement too
  much, forcing explicit opt in means types may implement too little.
  One way to mitigate this problem would be to have a lint for when an
  impl of some kind (etc) would be legal, but isn't implemented, at
  least for publicly exported types in library crates.

_______________________________________________
Rust-dev mailing list
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev


_______________________________________________
Rust-dev mailing list
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev
Reply | Threaded
Open this post in threaded view
|

Re: RFC: Opt-in builtin traits

Niko Matsakis
On Fri, Feb 28, 2014 at 08:15:18PM +0100, 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.

Buried in the the very last sentence was this idea:

"One way to mitigate this problem would be to have a lint for when an
 impl of some kind (etc) would be legal, but isn't implemented, at
 least for publicly exported types in library crates."

This seems to be similar to what you suggest.


Niko
_______________________________________________
Rust-dev mailing list
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev
Reply | Threaded
Open this post in threaded view
|

Re: RFC: Opt-in builtin traits

Matthieu Monrocq



On Fri, Feb 28, 2014 at 8:43 PM, Niko Matsakis <[hidden email]> wrote:
On Fri, Feb 28, 2014 at 08:15:18PM +0100, 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.

Buried in the the very last sentence was this idea:

"One way to mitigate this problem would be to have a lint for when an
 impl of some kind (etc) would be legal, but isn't implemented, at
 least for publicly exported types in library crates."

This seems to be similar to what you suggest.

Yes, I was actually rebounding on it.

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.

-- Matthieu
 

Niko


_______________________________________________
Rust-dev mailing list
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev
Reply | Threaded
Open this post in threaded view
|

Re: RFC: Opt-in builtin traits

Niko Matsakis
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
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev
Reply | Threaded
Open this post in threaded view
|

Re: RFC: Opt-in builtin traits

John Grosen
In reply to this post by Matthieu Monrocq
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
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev
Reply | Threaded
Open this post in threaded view
|

Re: RFC: Opt-in builtin traits

Gábor Lehel-2
In reply to this post by Niko Matsakis
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 `Mut<T>` type described
in #12577 -- the `Mut<T>` 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 <[hidden email]> 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 `Cell<int>` or
   `Atomic<int>`)
- `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`,
  `Atomic<int>`, not `Cell<int>`)

#### Proposed syntax

Under this proposal, for a struct or enum to be considered send,
freeze, pod, etc, those traits must be explicitly implemented:

    struct Foo { ... }
    impl Send for Foo { }
    impl Freeze for Foo { }
    impl Pod for Foo { }
    impl Share for Foo { }

For generic types, a conditional impl would be more appropriate:

    enum Option<T> { Some(T), None }
    impl<T:Send> Send for Option<T> { }
    // etc

As usual, deriving forms would be available that would expand into
impls like the one shown above.

Whenever a builtin trait is implemented, the compiler will enforce the
same requirements it enforces today. Therefore, code like the
following would yield an error:

    struct Foo<'a> { x: &'a int }

    // ERROR: Cannot implement `Send` because the field `x` has type
    // `&'a int` which is not sendable.
    impl<'a> Send for Foo<'a> { }

These impls would follow the usual coherence requirements. For
example, a struct can only be declared as `Share` within the crate
where it is defined.

For convenience, I also propose a deriving shorthand
`#[deriving(Data)]` that would implement a "package" of common traits
for types that contain simple data: `Eq`, `Ord`, `Clone`, `Show`,
`Send`, `Share`, `Freeze`, and `Pod`.

#### Pod and linearity

One of the most important aspects of this proposal is that the `Pod`
trait would be something that one "opts in" to. This means that
structs and enums would *move by default* unless their type is
explicitly declared to be `Pod`. So, for example, the following
code would be in error:

    struct Point { x: int, y: int }
    ...
    let p = Point { x: 1, y: 2 };
    let q = p;  // moves p
    print(p.x); // ERROR

To allow that example, one would have to impl `Pod` for `Point`:

    struct Point { x: int, y: int }
    impl Pod for Point { }
    ...
    let p = Point { x: 1, y: 2 };
    let q = p;  // copies p, because Point is Pod
    print(p.x); // OK

Effectively this change introduces a three step ladder for types:

1. If you do nothing, your type is *linear*, meaning that it moves
   from place to place and can never be copied in any way. (We need a
   better name for that.)
2. If you implement `Clone`, your type is *cloneable*, meaning that it
   moves from place to place, but it can be explicitly cloned. This is
   suitable for cases where copying is expensive.
3. If you implement `Pod`, your type is *plain old data*, meaning that
   it is just copied by default without the need for an explicit
   clone.  This is suitable for small bits of data like ints or
   points.

What is nice about this change is that when a type is defined, the
user makes an *explicit choice* between these three options.

#### Consistency

This change would bring the builtin traits more in line with other
common traits, such as `Eq` and `Clone`. On a historical note, this
proposal continues a trend, in that both of those operations used to
be natively implemented by the compiler as well.

#### API Stability

The set of builtin traits implemented by a type must be considered
part of its public inferface. At present, though, it's quite invisible
and not under user control. If a type is changed from `Pod` to
non-pod, or `Send` to non-send, no error message will result until
client code attempts to use an instance of that type. In general we
have tried to avoid this sort of situation, and instead have each
declaration contain enough information to check it indepenently of its
uses. Issue #12202 describes this same concern, specifically with
respect to stability attributes.

Making opt-in explicit effectively solves this problem. It is clearly
written out which traits a type is expected to fulfill, and if the
type is changed in such a way as to violate one of these traits, an
error will be reported at the `impl` site (or `#[deriving]`
declaration).

#### Pedagogy

When users first start with Rust, ownership and ownership transfer is
one of the first things that they must learn. This is made more
confusing by the fact that types are automatically divided into pod
and non-pod without any sort of declaration. It is not necessarily
obvious why a `T` and `~T` value, which are *semantically equivalent*,
behave so differently by default. Makes the pod category something you
opt into means that types will all be linear by default, which can
make teaching and leaning easier.

#### Safety and correctness: unsafe code

For safe code, the compiler's rules for deciding whether or not a type
is sendable (and so forth) are perfectly sound. However, when unsafe
code is involved, the compiler may draw the wrong conclusion. For such
cases, types must *opt out* of the builtin traits.

In general, the *opt out* approach seems to be hard to reason about:
many people (including myself) find it easier to think about what
properties a type *has* than what properties it *does not* have,
though clearly the two are logically equivalent in this binary world
we programmer's inhabit.

More concretely, opt out is dangerous because it means that types with
unsafe methods are generally *wrong by default*. As an example,
consider the definition of the `Cell` type:

    struct Cell<T> {
        priv value: T
    }

This is a perfectly ordinary struct, and hence the compiler would
conclude that cells are freezable (if `T` is freezable) and so forth.
However, the *methods* attached to `Cell` use unsafe magic to mutate
`value`, even when the `Cell` is aliased:

    impl<T:Pod> Cell<T> {
        pub fn set(&self, value: T) {
            unsafe {
                *cast::transmute_mut(&self.value) = value
            }
        }
    }

To accommodate this, we currently use *marker types* -- special types
known to the compiler which are considered nonpod and so forth. Therefore,
the full definition of `Cell` is in fact:

    pub struct Cell<T> {
        priv value: T,
        priv marker1: marker::InvariantType<T>,
        priv marker2: marker::NoFreeze,
    }

Note the two markers. The first, `marker1`, is a hint to the variance
engine indicating that the type `Cell` must be
[invariant with respect to its type argument][inv]. The second,
`marker2`, indicates that `Cell` is non-freeze. This then informs the
compiler that the referent of a `&Cell<T>` can't be considered
immutable. The problem here is that, if you don't know to opt-out,
you'll wind up with a type definition that is unsafe.

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 `Mut<T>` type described
in #12577 -- the `Mut<T>` type would give us the invariance.

Using `Mut<T>` brings us back to a world where any type that uses
`Mut<T>` to obtain interior mutability is correct by default, at least
with respect to the builtin kinds. Types like `Atomic<T>` and
`Volatile<T>`, which guarantee data race freedom, would therefore have
to *opt in* to the `Share` kind, and types like `Cell<T>` would simply
do nothing.

#### Safety and correctness: future compatibility

Another concern about having the compiler automatically infer
membership into builtin bounds is that we may find cause to add new
bounds in the future. In that case, existing Rust code which uses
unsafe methods might be inferred incorrectly, because it would not
know to opt out of those future bounds. Therefore, any future bounds
will *have* to be opt out anyway, so perhaps it is best to be
consistent from the start.

#### Safety and correctness: semantic constraints

Even if type safety is maintained, some types ought not to be copied
for semantic reasons. An example from the compiler is the
`Datum<Rvalue>` type, which is used in code generation to represent
the computed result of an rvalue expression. At present, the type
`Rvalue` implements a (empty) destructor -- the sole purpose of this
destructor is to ensure that datums are not consumed more than once,
because this would likely correspond to a code gen bug, as it would
mean that the result of the expression evaluation is consumed more
than once. Another example might be a newtype'd integer used for
indexing into a thread-local array: such a value ought not to be
sendable. And so forth. Using marker types for these kinds of
situations, or empty destructors, is very awkward. Under this
proposal, users needs merely refrain from implementing the relevant
traits.

#### The `Sized` bound

In DST, we plan to add a `Sized` bound. I do not feel like users
should manually implemented `Sized`. It seems tedious and rather
ludicrous.

#### Counterarguments

The downsides of this proposal are:

- There is some annotation burden. I had intended to gather statistics
  to try and measure this but have not had the time.

- If a library forgets to implement all the relevant traits for a
  type, there is little recourse for users of that library beyond pull
  requests to the original repository. This is already true with
  traits like `Eq` and `Ord`. However, as SiegeLord noted on IRC, that
  you can often work around the absence of `Eq` with a newtype
  wrapper, but this is not true if a type fails to implement `Send` or
  `Pod`. This danger (forgetting to implement traits) is essentially
  the counterbalance to the "forward compatbility" case made above:
  where implementing traits by default means types may implement too
  much, forcing explicit opt in means types may implement too little.
  One way to mitigate this problem would be to have a lint for when an
  impl of some kind (etc) would be legal, but isn't implemented, at
  least for publicly exported types in library crates.

_______________________________________________
Rust-dev mailing list
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev


_______________________________________________
Rust-dev mailing list
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev
Reply | Threaded
Open this post in threaded view
|

Re: RFC: Opt-in builtin traits

Corey Richardson
(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 <[hidden email]> 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 `Mut<T>` type described
> in #12577 -- the `Mut<T>` 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 <[hidden email]> 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 `Cell<int>` or
>>    `Atomic<int>`)
>> - `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`,
>>   `Atomic<int>`, not `Cell<int>`)
>>
>> #### Proposed syntax
>>
>> Under this proposal, for a struct or enum to be considered send,
>> freeze, pod, etc, those traits must be explicitly implemented:
>>
>>     struct Foo { ... }
>>     impl Send for Foo { }
>>     impl Freeze for Foo { }
>>     impl Pod for Foo { }
>>     impl Share for Foo { }
>>
>> For generic types, a conditional impl would be more appropriate:
>>
>>     enum Option<T> { Some(T), None }
>>     impl<T:Send> Send for Option<T> { }
>>     // etc
>>
>> As usual, deriving forms would be available that would expand into
>> impls like the one shown above.
>>
>> Whenever a builtin trait is implemented, the compiler will enforce the
>> same requirements it enforces today. Therefore, code like the
>> following would yield an error:
>>
>>     struct Foo<'a> { x: &'a int }
>>
>>     // ERROR: Cannot implement `Send` because the field `x` has type
>>     // `&'a int` which is not sendable.
>>     impl<'a> Send for Foo<'a> { }
>>
>> These impls would follow the usual coherence requirements. For
>> example, a struct can only be declared as `Share` within the crate
>> where it is defined.
>>
>> For convenience, I also propose a deriving shorthand
>> `#[deriving(Data)]` that would implement a "package" of common traits
>> for types that contain simple data: `Eq`, `Ord`, `Clone`, `Show`,
>> `Send`, `Share`, `Freeze`, and `Pod`.
>>
>> #### Pod and linearity
>>
>> One of the most important aspects of this proposal is that the `Pod`
>> trait would be something that one "opts in" to. This means that
>> structs and enums would *move by default* unless their type is
>> explicitly declared to be `Pod`. So, for example, the following
>> code would be in error:
>>
>>     struct Point { x: int, y: int }
>>     ...
>>     let p = Point { x: 1, y: 2 };
>>     let q = p;  // moves p
>>     print(p.x); // ERROR
>>
>> To allow that example, one would have to impl `Pod` for `Point`:
>>
>>     struct Point { x: int, y: int }
>>     impl Pod for Point { }
>>     ...
>>     let p = Point { x: 1, y: 2 };
>>     let q = p;  // copies p, because Point is Pod
>>     print(p.x); // OK
>>
>> Effectively this change introduces a three step ladder for types:
>>
>> 1. If you do nothing, your type is *linear*, meaning that it moves
>>    from place to place and can never be copied in any way. (We need a
>>    better name for that.)
>> 2. If you implement `Clone`, your type is *cloneable*, meaning that it
>>    moves from place to place, but it can be explicitly cloned. This is
>>    suitable for cases where copying is expensive.
>> 3. If you implement `Pod`, your type is *plain old data*, meaning that
>>    it is just copied by default without the need for an explicit
>>    clone.  This is suitable for small bits of data like ints or
>>    points.
>>
>> What is nice about this change is that when a type is defined, the
>> user makes an *explicit choice* between these three options.
>>
>> #### Consistency
>>
>> This change would bring the builtin traits more in line with other
>> common traits, such as `Eq` and `Clone`. On a historical note, this
>> proposal continues a trend, in that both of those operations used to
>> be natively implemented by the compiler as well.
>>
>> #### API Stability
>>
>> The set of builtin traits implemented by a type must be considered
>> part of its public inferface. At present, though, it's quite invisible
>> and not under user control. If a type is changed from `Pod` to
>> non-pod, or `Send` to non-send, no error message will result until
>> client code attempts to use an instance of that type. In general we
>> have tried to avoid this sort of situation, and instead have each
>> declaration contain enough information to check it indepenently of its
>> uses. Issue #12202 describes this same concern, specifically with
>> respect to stability attributes.
>>
>> Making opt-in explicit effectively solves this problem. It is clearly
>> written out which traits a type is expected to fulfill, and if the
>> type is changed in such a way as to violate one of these traits, an
>> error will be reported at the `impl` site (or `#[deriving]`
>> declaration).
>>
>> #### Pedagogy
>>
>> When users first start with Rust, ownership and ownership transfer is
>> one of the first things that they must learn. This is made more
>> confusing by the fact that types are automatically divided into pod
>> and non-pod without any sort of declaration. It is not necessarily
>> obvious why a `T` and `~T` value, which are *semantically equivalent*,
>> behave so differently by default. Makes the pod category something you
>> opt into means that types will all be linear by default, which can
>> make teaching and leaning easier.
>>
>> #### Safety and correctness: unsafe code
>>
>> For safe code, the compiler's rules for deciding whether or not a type
>> is sendable (and so forth) are perfectly sound. However, when unsafe
>> code is involved, the compiler may draw the wrong conclusion. For such
>> cases, types must *opt out* of the builtin traits.
>>
>> In general, the *opt out* approach seems to be hard to reason about:
>> many people (including myself) find it easier to think about what
>> properties a type *has* than what properties it *does not* have,
>> though clearly the two are logically equivalent in this binary world
>> we programmer's inhabit.
>>
>> More concretely, opt out is dangerous because it means that types with
>> unsafe methods are generally *wrong by default*. As an example,
>> consider the definition of the `Cell` type:
>>
>>     struct Cell<T> {
>>         priv value: T
>>     }
>>
>> This is a perfectly ordinary struct, and hence the compiler would
>> conclude that cells are freezable (if `T` is freezable) and so forth.
>> However, the *methods* attached to `Cell` use unsafe magic to mutate
>> `value`, even when the `Cell` is aliased:
>>
>>     impl<T:Pod> Cell<T> {
>>         pub fn set(&self, value: T) {
>>             unsafe {
>>                 *cast::transmute_mut(&self.value) = value
>>             }
>>         }
>>     }
>>
>> To accommodate this, we currently use *marker types* -- special types
>> known to the compiler which are considered nonpod and so forth. Therefore,
>> the full definition of `Cell` is in fact:
>>
>>     pub struct Cell<T> {
>>         priv value: T,
>>         priv marker1: marker::InvariantType<T>,
>>         priv marker2: marker::NoFreeze,
>>     }
>>
>> Note the two markers. The first, `marker1`, is a hint to the variance
>> engine indicating that the type `Cell` must be
>> [invariant with respect to its type argument][inv]. The second,
>> `marker2`, indicates that `Cell` is non-freeze. This then informs the
>> compiler that the referent of a `&Cell<T>` can't be considered
>> immutable. The problem here is that, if you don't know to opt-out,
>> you'll wind up with a type definition that is unsafe.
>>
>> 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 `Mut<T>` type described
>> in #12577 -- the `Mut<T>` type would give us the invariance.
>>
>> Using `Mut<T>` brings us back to a world where any type that uses
>> `Mut<T>` to obtain interior mutability is correct by default, at least
>> with respect to the builtin kinds. Types like `Atomic<T>` and
>> `Volatile<T>`, which guarantee data race freedom, would therefore have
>> to *opt in* to the `Share` kind, and types like `Cell<T>` would simply
>> do nothing.
>>
>> #### Safety and correctness: future compatibility
>>
>> Another concern about having the compiler automatically infer
>> membership into builtin bounds is that we may find cause to add new
>> bounds in the future. In that case, existing Rust code which uses
>> unsafe methods might be inferred incorrectly, because it would not
>> know to opt out of those future bounds. Therefore, any future bounds
>> will *have* to be opt out anyway, so perhaps it is best to be
>> consistent from the start.
>>
>> #### Safety and correctness: semantic constraints
>>
>> Even if type safety is maintained, some types ought not to be copied
>> for semantic reasons. An example from the compiler is the
>> `Datum<Rvalue>` type, which is used in code generation to represent
>> the computed result of an rvalue expression. At present, the type
>> `Rvalue` implements a (empty) destructor -- the sole purpose of this
>> destructor is to ensure that datums are not consumed more than once,
>> because this would likely correspond to a code gen bug, as it would
>> mean that the result of the expression evaluation is consumed more
>> than once. Another example might be a newtype'd integer used for
>> indexing into a thread-local array: such a value ought not to be
>> sendable. And so forth. Using marker types for these kinds of
>> situations, or empty destructors, is very awkward. Under this
>> proposal, users needs merely refrain from implementing the relevant
>> traits.
>>
>> #### The `Sized` bound
>>
>> In DST, we plan to add a `Sized` bound. I do not feel like users
>> should manually implemented `Sized`. It seems tedious and rather
>> ludicrous.
>>
>> #### Counterarguments
>>
>> The downsides of this proposal are:
>>
>> - There is some annotation burden. I had intended to gather statistics
>>   to try and measure this but have not had the time.
>>
>> - If a library forgets to implement all the relevant traits for a
>>   type, there is little recourse for users of that library beyond pull
>>   requests to the original repository. This is already true with
>>   traits like `Eq` and `Ord`. However, as SiegeLord noted on IRC, that
>>   you can often work around the absence of `Eq` with a newtype
>>   wrapper, but this is not true if a type fails to implement `Send` or
>>   `Pod`. This danger (forgetting to implement traits) is essentially
>>   the counterbalance to the "forward compatbility" case made above:
>>   where implementing traits by default means types may implement too
>>   much, forcing explicit opt in means types may implement too little.
>>   One way to mitigate this problem would be to have a lint for when an
>>   impl of some kind (etc) would be legal, but isn't implemented, at
>>   least for publicly exported types in library crates.
>>
>> _______________________________________________
>> Rust-dev mailing list
>> [hidden email]
>> https://mail.mozilla.org/listinfo/rust-dev
>
>
>
> _______________________________________________
> Rust-dev mailing list
> [hidden email]
> https://mail.mozilla.org/listinfo/rust-dev
>
_______________________________________________
Rust-dev mailing list
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev
Reply | Threaded
Open this post in threaded view
|

Re: RFC: Opt-in builtin traits

Kang Seonghoon
In reply to this post by John Grosen
2014-03-01 6:24 GMT+09:00 John Grosen <[hidden email]>:

>> 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.

--
-- Kang Seonghoon | Software Engineer, iPlateia Inc. | http://mearie.org/
-- Opinions expressed in this email do not necessarily represent the
views of my employer.
--
_______________________________________________
Rust-dev mailing list
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev
Reply | Threaded
Open this post in threaded view
|

Re: RFC: Opt-in builtin traits

Kevin Ballard-2
On Feb 28, 2014, at 8:10 PM, Kang Seonghoon <[hidden email]> wrote:

> 2014-03-01 6:24 GMT+09:00 John Grosen <[hidden email]>:
>>> 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
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev
Reply | Threaded
Open this post in threaded view
|

Re: RFC: Opt-in builtin traits

Gareth Smith
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
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev
Reply | Threaded
Open this post in threaded view
|

Re: RFC: Opt-in builtin traits

Niko Matsakis
In reply to this post by Gábor Lehel-2
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 MyVec<T> {
        r: Rc<Vec<T>>
    }

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
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev
Reply | Threaded
Open this post in threaded view
|

Re: RFC: Opt-in builtin traits

Niko Matsakis
In reply to this post by Kevin Ballard-2
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
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev
Reply | Threaded
Open this post in threaded view
|

Re: RFC: Opt-in builtin traits

Liigo Zhuang

I like this

2014年3月2日 上午4:07于 "Niko Matsakis" <[hidden email]>写道:
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
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev

_______________________________________________
Rust-dev mailing list
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev
Reply | Threaded
Open this post in threaded view
|

Re: RFC: Opt-in builtin traits

Gábor Lehel-2
In reply to this post by Niko Matsakis



On Sat, Mar 1, 2014 at 9:07 PM, Niko Matsakis <[hidden email]> 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
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev


_______________________________________________
Rust-dev mailing list
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev
Reply | Threaded
Open this post in threaded view
|

Re: RFC: Opt-in builtin traits

Gábor Lehel-2
In reply to this post by Niko Matsakis



On Sat, Mar 1, 2014 at 8:24 PM, Niko Matsakis <[hidden email]> 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 Option<T> { 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 Option<T> 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 Option<T> { 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 Option<T> { 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 MyVec<T> {
        r: Rc<Vec<T>>
    }

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>`.

- 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.

(I think you meant "via lifetimes"? Or maybe "via inference"?)

My Coercible proposal also relies on variance for its equivalent to substructs, so I think we're going to need it no matter what, if we want this kind of capability (i.e. coercions/reinterpretations that are legal only in one direction).
 

  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.

Interesting, I thought subtyping of lifetimes was important for the system to work ergonomically? Apparently that's not the case?
 

- 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.

It's unfortunate that this is in tension with covariant being the most common case in practice, but it really doesn't make sense to choose any default other than the one which assumes the least, here invariant. Otherwise you're back to the situation that things like `Cell` are unsafe by default. And it's not just interior mutability which causes a failure of covariance: obviously using the type in function argument position will do the same... as will using it as argument to any invariant type parameter of another generic type. HashMap is also invariant in its first argument. Etc.
 

Lots to think about!

I'm not even sure if that's the harder part, or writing it all down. But for sure thinking is more enjoyable. :)
 


Niko


_______________________________________________
Rust-dev mailing list
[hidden email]
https://mail.mozilla.org/listinfo/rust-dev