New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Opt-in builtin traits, take 2: default and negative impls. #127

Merged
merged 5 commits into from Sep 18, 2014

Conversation

Projects
None yet
9 participants
@nikomatsakis
Contributor

nikomatsakis commented Jun 17, 2014

The high-level idea is to add language features that simultaneously
achieve three goals:

  1. move Send and Share out of the language entirely and into the
    standard library, providing mechanisms for end users to easily
    implement and use similar "marker" traits of their own devising;
  2. make "normal" Rust types sendable and sharable by default, without
    the need for explicit opt-in; and,
  3. continue to require "unsafe" Rust types (those that manipulate
    unsafe pointers or implement special abstractions) to "opt-in" to
    sendability and sharability with an unsafe declaration.
@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis
Contributor

nikomatsakis commented Jun 17, 2014

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jun 18, 2014

Contributor

cc @pnkfelix -- I'd specifically like feedback re: the GC discussion.

Contributor

nikomatsakis commented Jun 18, 2014

cc @pnkfelix -- I'd specifically like feedback re: the GC discussion.

@bstrie

This comment has been minimized.

Show comment
Hide comment
@bstrie

bstrie Jun 18, 2014

Contributor

Can you imagine any other potential use of unsafe trait other than for default traits?

Contributor

bstrie commented Jun 18, 2014

Can you imagine any other potential use of unsafe trait other than for default traits?

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jun 18, 2014

Contributor

@bstrie I think so, yes.

On the one hand, any trait with methods can declare those methods as unsafe, but that's not quite the same thing -- that's saying that the methods cannot themselves check all the conditions that would be required to show they are safe (and hence the caller must be relied upon to do so).

I imagine you would want to use an unsafe trait to signal that the method is running in an unsafe environment where it is expected not to make use of certain facilities, even though we can't stop it from doing so, or where it is expected to maintain other invariants. For example, imagine a trait Traceable that ran during the GC and was used to trace through smart pointers. That would probably be an unsafe trait because the code within will run asynchronously with respect to the main thread and thus it cannot touch any ref cells or locks without fear of inducing deadlock. It may also be expected not to fail or to uphold other similar "above the call of duty" invariants.

To summarize, the role of the unsafe keyword depends on context:

  • on a fn or method, it signals "extra conditions are present on the caller"
  • on a block, it signals "this block must prove extra conditions"
  • on an trait, it would signal "extra conditions are present on implementors"
  • on an impl, it would signal "this impl must prove extra conditions"

You can use unsafe on both a trait and the methods in a trait to signal extra conditions both ways. :)

Contributor

nikomatsakis commented Jun 18, 2014

@bstrie I think so, yes.

On the one hand, any trait with methods can declare those methods as unsafe, but that's not quite the same thing -- that's saying that the methods cannot themselves check all the conditions that would be required to show they are safe (and hence the caller must be relied upon to do so).

I imagine you would want to use an unsafe trait to signal that the method is running in an unsafe environment where it is expected not to make use of certain facilities, even though we can't stop it from doing so, or where it is expected to maintain other invariants. For example, imagine a trait Traceable that ran during the GC and was used to trace through smart pointers. That would probably be an unsafe trait because the code within will run asynchronously with respect to the main thread and thus it cannot touch any ref cells or locks without fear of inducing deadlock. It may also be expected not to fail or to uphold other similar "above the call of duty" invariants.

To summarize, the role of the unsafe keyword depends on context:

  • on a fn or method, it signals "extra conditions are present on the caller"
  • on a block, it signals "this block must prove extra conditions"
  • on an trait, it would signal "extra conditions are present on implementors"
  • on an impl, it would signal "this impl must prove extra conditions"

You can use unsafe on both a trait and the methods in a trait to signal extra conditions both ways. :)

@pnkfelix

This comment has been minimized.

Show comment
Hide comment
@pnkfelix

pnkfelix Jun 18, 2014

Member

I think the RFC needs to elaborate on the interaction of this feature with trait objects (i.e. bounds).

In particular: I assume that something here is going to automatically become a candidate for being listed as a bound on a trait bound (or a closure), to fit with the current usage of Send as a trait bound when necessary.

The question is: Is it unsafe traits that become such bounds? Or traits with a default impl?

Member

pnkfelix commented Jun 18, 2014

I think the RFC needs to elaborate on the interaction of this feature with trait objects (i.e. bounds).

In particular: I assume that something here is going to automatically become a candidate for being listed as a bound on a trait bound (or a closure), to fit with the current usage of Send as a trait bound when necessary.

The question is: Is it unsafe traits that become such bounds? Or traits with a default impl?

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jun 18, 2014

Contributor

@pnkfelix Yes, a good point! I thought of that as I was traveling home last night. I was making the assumption that we would permit arbitrary traits to be listed as bounds, but of course that merits another RFC, and for the time being I'd probably be inclined to limit "extra" bounds on trait objects to traits with no methods, so as to avoid having to codegen the vtables.

Contributor

nikomatsakis commented Jun 18, 2014

@pnkfelix Yes, a good point! I thought of that as I was traveling home last night. I was making the assumption that we would permit arbitrary traits to be listed as bounds, but of course that merits another RFC, and for the time being I'd probably be inclined to limit "extra" bounds on trait objects to traits with no methods, so as to avoid having to codegen the vtables.

@LeoTestard

This comment has been minimized.

Show comment
Hide comment
@LeoTestard

LeoTestard Jun 23, 2014

Contributor

Couldn't this be extended to the Copy trait too ? Something like

unsafe trait Copy { }

impl Copy for .. { }

// disallow shallow copy for types with destructors. add more negative
// impls here if needed, I don't know what our exact rules for being Copy
// are at the moment
impl<T: Drop> !Copy for T { }

Of course, Copy should still be understood by the compiler, but this would allow to override current rules with unsafe impl. Maybe it would also make changing the rules for being Copy easier. I have absolutely no idea whether this would be a good idea or not though, just something that came to my mind, and I'm curious to know what you think about it.

Contributor

LeoTestard commented Jun 23, 2014

Couldn't this be extended to the Copy trait too ? Something like

unsafe trait Copy { }

impl Copy for .. { }

// disallow shallow copy for types with destructors. add more negative
// impls here if needed, I don't know what our exact rules for being Copy
// are at the moment
impl<T: Drop> !Copy for T { }

Of course, Copy should still be understood by the compiler, but this would allow to override current rules with unsafe impl. Maybe it would also make changing the rules for being Copy easier. I have absolutely no idea whether this would be a good idea or not though, just something that came to my mind, and I'm curious to know what you think about it.

@kballard

This comment has been minimized.

Show comment
Hide comment
@kballard

kballard Jul 29, 2014

Contributor

@LeoTestard I don't think it makes sense for Copy, because a type that is inferred to not be Copy cannot have Copy "safety" added back in later. By that I mean that if a type isn't Copy because the type of one of its fields isn't, there is no way for that type to somehow make itself safe to memcpy after all, so there is no situation in which it makes sense for a type to ever opt back in to Copy.

Contributor

kballard commented Jul 29, 2014

@LeoTestard I don't think it makes sense for Copy, because a type that is inferred to not be Copy cannot have Copy "safety" added back in later. By that I mean that if a type isn't Copy because the type of one of its fields isn't, there is no way for that type to somehow make itself safe to memcpy after all, so there is no situation in which it makes sense for a type to ever opt back in to Copy.

We add a notion of a *default impl*, written:
impl Trait for .. { }

This comment has been minimized.

@liigo

liigo Sep 17, 2014

Contributor

Maybe _ is better? impl Trait for _ {}

@liigo

liigo Sep 17, 2014

Contributor

Maybe _ is better? impl Trait for _ {}

This comment has been minimized.

@pnkfelix

pnkfelix Sep 17, 2014

Member

I think the motivation is via analogy with the .. wildcard when matching fields of a struct: .. stands for any set of values except for the ones listed explicitly, i.e.:

fn main() {
    struct S { x: i8, y: i8, z: i8 };
    let s = S { x: 1, y: 2, z: 3 };
    match s {
        S { x, .. } => println!("x: {}", x)
    }
}

Because of the precedent illustrated above, I think .. denotes well the notions that

  1. many implementations are being introduced by this impl, and
  2. there may be exceptions to this impl.
@pnkfelix

pnkfelix Sep 17, 2014

Member

I think the motivation is via analogy with the .. wildcard when matching fields of a struct: .. stands for any set of values except for the ones listed explicitly, i.e.:

fn main() {
    struct S { x: i8, y: i8, z: i8 };
    let s = S { x: 1, y: 2, z: 3 };
    match s {
        S { x, .. } => println!("x: {}", x)
    }
}

Because of the precedent illustrated above, I think .. denotes well the notions that

  1. many implementations are being introduced by this impl, and
  2. there may be exceptions to this impl.

This comment has been minimized.

@liigo

liigo Sep 18, 2014

Contributor

_ means "any (other) value/type omitting it's name here".
Rust already uses it in let and match statements in variant ways:

let p = box 1;
let _ = p;
let (x, _) = (Some(1), Some(2));
match x {
  Some(_) => { }
  _ => { }
}

I think _ denotes well the notions 1 and 2 from @pnkfelix above, and
3. _ is shorter than ...
4. .. was already used in rangex..y array[..N] and slice[a..b] syntax, in other significations.

@liigo

liigo Sep 18, 2014

Contributor

_ means "any (other) value/type omitting it's name here".
Rust already uses it in let and match statements in variant ways:

let p = box 1;
let _ = p;
let (x, _) = (Some(1), Some(2));
match x {
  Some(_) => { }
  _ => { }
}

I think _ denotes well the notions 1 and 2 from @pnkfelix above, and
3. _ is shorter than ...
4. .. was already used in rangex..y array[..N] and slice[a..b] syntax, in other significations.

This comment has been minimized.

@pnkfelix

pnkfelix Sep 18, 2014

Member

How does _ denote notion 2? It accepts anything, not "anything except ..."

the fact that one can use it as a catch-all in match after a series of other clauses seems like more an artifact of the semantics of match rather than inherent to _ itself.

@pnkfelix

pnkfelix Sep 18, 2014

Member

How does _ denote notion 2? It accepts anything, not "anything except ..."

the fact that one can use it as a catch-all in match after a series of other clauses seems like more an artifact of the semantics of match rather than inherent to _ itself.

This comment has been minimized.

@nikomatsakis

nikomatsakis Sep 19, 2014

Contributor

On Thu, Sep 18, 2014 at 02:15:26AM -0700, Liigo Zhuang wrote:

_ means "any (other) value/type omitting it's name here".

The key point is that impl Trait for Foo { } is very different from
applying an impl Trait for .. { } to Foo -- the latter walks down
recursively, applying a test to the parameter types, and the former
does not. _ does not imply this recursive walk to me.

@nikomatsakis

nikomatsakis Sep 19, 2014

Contributor

On Thu, Sep 18, 2014 at 02:15:26AM -0700, Liigo Zhuang wrote:

_ means "any (other) value/type omitting it's name here".

The key point is that impl Trait for Foo { } is very different from
applying an impl Trait for .. { } to Foo -- the latter walks down
recursively, applying a test to the parameter types, and the former
does not. _ does not imply this recursive walk to me.

## The `Copy` and `Sized` traits
The final two builtin traits are `Copy` and `Share`. This RFC does not

This comment has been minimized.

@liigo

liigo Sep 17, 2014

Contributor

Share => Sized

@liigo

liigo Sep 17, 2014

Contributor

Share => Sized

@brendanzab

This comment has been minimized.

Show comment
Hide comment
@brendanzab

brendanzab Sep 18, 2014

Member

From a cursory look, this seems like a great solution!

Member

brendanzab commented Sep 18, 2014

From a cursory look, this seems like a great solution!

@pcwalton pcwalton merged commit 2860ed8 into rust-lang:master Sep 18, 2014

pnkfelix added a commit to pnkfelix/rfcs that referenced this pull request Oct 8, 2014

Fixed typos and format inconsistencies in headers of various RFCs.
In particular:

* The RFC associated with #127 should have had a link to #19 as well
  (and has been assigned RFC #19); it also was revised to match the
  markdown href style of other RFCs.

* RFC #34 needed its header entries filled in,

* RFC #123 had a typo in its header, and

* RC #155 was revised to match the markdown href style of other RFCs.

bors added a commit to rust-lang/rust that referenced this pull request Oct 21, 2014

auto merge of #17864 : pcwalton/rust/oibit2, r=nikomatsakis
This change makes the compiler no longer infer whether types (structures
and enumerations) implement the `Copy` trait (and thus are implicitly
copyable). Rather, you must implement `Copy` yourself via `impl Copy for
MyType {}`.

This breaks code like:

    #[deriving(Show)]
    struct Point2D {
        x: int,
        y: int,
    }

    fn main() {
        let mypoint = Point2D {
            x: 1,
            y: 1,
        };
        let otherpoint = mypoint;
        println!("{}{}", mypoint, otherpoint);
    }

Change this code to:

    #[deriving(Show)]
    struct Point2D {
        x: int,
        y: int,
    }

    impl Copy for Point2D {}

    fn main() {
        let mypoint = Point2D {
            x: 1,
            y: 1,
        };
        let otherpoint = mypoint;
        println!("{}{}", mypoint, otherpoint);
    }

This is the backwards-incompatible part of #13231.

Part of [RFC #3](rust-lang/rfcs#127).

[breaking-change]

r? @nikomatsakis

bors added a commit to rust-lang/rust that referenced this pull request Oct 23, 2014

auto merge of #17864 : pcwalton/rust/oibit2, r=nikomatsakis
This change makes the compiler no longer infer whether types (structures
and enumerations) implement the `Copy` trait (and thus are implicitly
copyable). Rather, you must implement `Copy` yourself via `impl Copy for
MyType {}`.

This breaks code like:

    #[deriving(Show)]
    struct Point2D {
        x: int,
        y: int,
    }

    fn main() {
        let mypoint = Point2D {
            x: 1,
            y: 1,
        };
        let otherpoint = mypoint;
        println!("{}{}", mypoint, otherpoint);
    }

Change this code to:

    #[deriving(Show)]
    struct Point2D {
        x: int,
        y: int,
    }

    impl Copy for Point2D {}

    fn main() {
        let mypoint = Point2D {
            x: 1,
            y: 1,
        };
        let otherpoint = mypoint;
        println!("{}{}", mypoint, otherpoint);
    }

This is the backwards-incompatible part of #13231.

Part of [RFC #3](rust-lang/rfcs#127).

[breaking-change]

r? @nikomatsakis

bors added a commit to rust-lang/rust that referenced this pull request Dec 5, 2014

auto merge of #17864 : pcwalton/rust/oibit2, r=nmatsakis
This change makes the compiler no longer infer whether types (structures
and enumerations) implement the `Copy` trait (and thus are implicitly
copyable). Rather, you must implement `Copy` yourself via `impl Copy for
MyType {}`.

This breaks code like:

    #[deriving(Show)]
    struct Point2D {
        x: int,
        y: int,
    }

    fn main() {
        let mypoint = Point2D {
            x: 1,
            y: 1,
        };
        let otherpoint = mypoint;
        println!("{}{}", mypoint, otherpoint);
    }

Change this code to:

    #[deriving(Show)]
    struct Point2D {
        x: int,
        y: int,
    }

    impl Copy for Point2D {}

    fn main() {
        let mypoint = Point2D {
            x: 1,
            y: 1,
        };
        let otherpoint = mypoint;
        println!("{}{}", mypoint, otherpoint);
    }

This is the backwards-incompatible part of #13231.

Part of [RFC #3](rust-lang/rfcs#127).

[breaking-change]

r? @nikomatsakis

bors added a commit to rust-lang/rust that referenced this pull request Dec 5, 2014

auto merge of #17864 : pcwalton/rust/oibit2, r=nmatsakis
This change makes the compiler no longer infer whether types (structures
and enumerations) implement the `Copy` trait (and thus are implicitly
copyable). Rather, you must implement `Copy` yourself via `impl Copy for
MyType {}`.

This breaks code like:

    #[deriving(Show)]
    struct Point2D {
        x: int,
        y: int,
    }

    fn main() {
        let mypoint = Point2D {
            x: 1,
            y: 1,
        };
        let otherpoint = mypoint;
        println!("{}{}", mypoint, otherpoint);
    }

Change this code to:

    #[deriving(Show)]
    struct Point2D {
        x: int,
        y: int,
    }

    impl Copy for Point2D {}

    fn main() {
        let mypoint = Point2D {
            x: 1,
            y: 1,
        };
        let otherpoint = mypoint;
        println!("{}{}", mypoint, otherpoint);
    }

This is the backwards-incompatible part of #13231.

Part of [RFC #3](rust-lang/rfcs#127).

[breaking-change]

r? @nikomatsakis

bors added a commit to rust-lang/rust that referenced this pull request Jan 4, 2015

auto merge of #20285 : FlaPer87/rust/oibit-send-and-friends, r=nikoma…
…tsakis

This commit introduces the syntax for negative implementations of traits
as shown below:

`impl !Trait for Type {}`

cc #13231
Part of RFC rust-lang/rfcs#127

r? @nikomatsakis

bors added a commit to rust-lang/rust that referenced this pull request Jan 5, 2015

auto merge of #20285 : FlaPer87/rust/oibit-send-and-friends, r=nikoma…
…tsakis

This commit introduces the syntax for negative implementations of traits
as shown below:

`impl !Trait for Type {}`

cc #13231
Part of RFC rust-lang/rfcs#127

r? @nikomatsakis
@keean

This comment has been minimized.

Show comment
Hide comment
@keean

keean Apr 21, 2016

Hope it's not too late to join the conversation. I think Opt-out traits seem to have the same problems as negative trait bounds, for example:

trait NoDisplay {}
impl NoDisplay for .. {}
impl<T> !NoDisplay for T where T : Display {}
fn foo<T: Display>(data: T) { panic!("{}", data) }
fn foo<T: NoDisplay>(_: T) { println!("Can't display") }

keean commented Apr 21, 2016

Hope it's not too late to join the conversation. I think Opt-out traits seem to have the same problems as negative trait bounds, for example:

trait NoDisplay {}
impl NoDisplay for .. {}
impl<T> !NoDisplay for T where T : Display {}
fn foo<T: Display>(data: T) { panic!("{}", data) }
fn foo<T: NoDisplay>(_: T) { println!("Can't display") }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment