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

Minimal `impl Trait` #1522

Merged
merged 19 commits into from Jun 27, 2016

Conversation

Projects
None yet
@Kimundi
Member

Kimundi commented Mar 1, 2016

Add a initial, minimal form of impl Trait.

Rendered

[edited to link to final rendered version]

@eddyb

This comment has been minimized.

Show comment
Hide comment
@eddyb

eddyb Mar 1, 2016

Member

I should mention that being able to use @Trait within an associated type in a trait impl (which can be "fulfilled" by any of the methods in the same impl) provides maximal expressiveness:

  • a name for the anonymized type (type Anon = <T as Trait>::Assoc)
  • the ability to have extension methods that effectively return @Trait (and not just free functions)
  • regular old trait implementations that operate with types they can't name (e.g. an iterator that produces more iterators, containing closures)

That extension to this RFC was essential to my calendar demo implementation from last year, and had a small implementation footprint.

Member

eddyb commented Mar 1, 2016

I should mention that being able to use @Trait within an associated type in a trait impl (which can be "fulfilled" by any of the methods in the same impl) provides maximal expressiveness:

  • a name for the anonymized type (type Anon = <T as Trait>::Assoc)
  • the ability to have extension methods that effectively return @Trait (and not just free functions)
  • regular old trait implementations that operate with types they can't name (e.g. an iterator that produces more iterators, containing closures)

That extension to this RFC was essential to my calendar demo implementation from last year, and had a small implementation footprint.

Made it clear that function pointers may still include @trait if used…
… as part of a return type of a freestanding function
@arielb1

This comment has been minimized.

Show comment
Hide comment
@arielb1

arielb1 Mar 1, 2016

Contributor

Type parameters and trait objects seem to work quite fine without OIBIT passthrough, so maybe this could work there too?

Contributor

arielb1 commented Mar 1, 2016

Type parameters and trait objects seem to work quite fine without OIBIT passthrough, so maybe this could work there too?

@steveklabnik

This comment has been minimized.

Show comment
Hide comment
@steveklabnik

steveklabnik Mar 1, 2016

Member

Bikeshed checking in: I strongly prefer a keyword like impl Trait over @Trait, as it's only barely longer, and is significantly more discoverable.

Member

steveklabnik commented Mar 1, 2016

Bikeshed checking in: I strongly prefer a keyword like impl Trait over @Trait, as it's only barely longer, and is significantly more discoverable.

@ticki

This comment has been minimized.

Show comment
Hide comment
@ticki

ticki Mar 1, 2016

Contributor

@steveklabnik I agree. @ seems like a very noisy sigil to reintroduce. However, impl isn't fit for this purpose, since this feature is like going to be used a lot, and abusing impl isn't really something we want to.

Contributor

ticki commented Mar 1, 2016

@steveklabnik I agree. @ seems like a very noisy sigil to reintroduce. However, impl isn't fit for this purpose, since this feature is like going to be used a lot, and abusing impl isn't really something we want to.

@eddyb

This comment has been minimized.

Show comment
Hide comment
@eddyb

eddyb Mar 1, 2016

Member

@steveklabnik Compare:

fn foo(x: bool) -> @Fn(&str) -> @Fn(i32) -> String {...}
fn foo(x: bool) -> impl Fn(&str) -> impl Fn(i32) -> String {...}
Member

eddyb commented Mar 1, 2016

@steveklabnik Compare:

fn foo(x: bool) -> @Fn(&str) -> @Fn(i32) -> String {...}
fn foo(x: bool) -> impl Fn(&str) -> impl Fn(i32) -> String {...}
@steveklabnik

This comment has been minimized.

Show comment
Hide comment
@steveklabnik

steveklabnik Mar 1, 2016

Member

@ticki that might be fair, my preference is "keyword" over "sigil", the specific keyword is less important. I'm not totally sure how this is "absuing" impl, it's about symmetry.

@eddyb Sure. Prefer the second.

Member

steveklabnik commented Mar 1, 2016

@ticki that might be fair, my preference is "keyword" over "sigil", the specific keyword is less important. I'm not totally sure how this is "absuing" impl, it's about symmetry.

@eddyb Sure. Prefer the second.

@steveklabnik

This comment has been minimized.

Show comment
Hide comment
@steveklabnik

steveklabnik Mar 1, 2016

Member

Anyway, I don't want to distract with the bikeshed here: Let's worry about semantics first. I will refrain from further expressing my preferences here until the end 😄

Member

steveklabnik commented Mar 1, 2016

Anyway, I don't want to distract with the bikeshed here: Let's worry about semantics first. I will refrain from further expressing my preferences here until the end 😄

@comex

This comment has been minimized.

Show comment
Hide comment
@comex

comex Mar 2, 2016

+1 on impl over @.

Perhaps impl could be optional: writing impl Trait would always work, including in future extensions, but in unambiguous cases such as function return types, you could just write -> Trait. However, that might be confusing.

comex commented Mar 2, 2016

+1 on impl over @.

Perhaps impl could be optional: writing impl Trait would always work, including in future extensions, but in unambiguous cases such as function return types, you could just write -> Trait. However, that might be confusing.

@aturon aturon self-assigned this Mar 2, 2016

@Kimundi

This comment has been minimized.

Show comment
Hide comment
@Kimundi

Kimundi Mar 2, 2016

Member

@comex That would be a bit confusing though, and ambiguous with the hypothetical-but-talked-about extension to return DST values. (Which is theoretically possible given outpointers and DST-allocator/box support)

Member

Kimundi commented Mar 2, 2016

@comex That would be a bit confusing though, and ambiguous with the hypothetical-but-talked-about extension to return DST values. (Which is theoretically possible given outpointers and DST-allocator/box support)

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Mar 2, 2016

Contributor

I'm -1 on having no prefix at all; among other things, I'd eventually like to be able to use impl Trait in method argument position, and possibly other positions as well.

Contributor

nikomatsakis commented Mar 2, 2016

I'm -1 on having no prefix at all; among other things, I'd eventually like to be able to use impl Trait in method argument position, and possibly other positions as well.

- The type would be known to implement the specified traits.
- The type would not be known to implement any other trait, with
the exception of OIBITS (aka "auto traits") and default traits like `Sized`.

This comment has been minimized.

@petrochenkov

petrochenkov Mar 2, 2016

Contributor

Can the part about OIBITs be omitted at least initially, until the usage experience provides the evidence, that this is really really necessary and unavoidable?

@petrochenkov

petrochenkov Mar 2, 2016

Contributor

Can the part about OIBITs be omitted at least initially, until the usage experience provides the evidence, that this is really really necessary and unavoidable?

This comment has been minimized.

@nikomatsakis

nikomatsakis Mar 2, 2016

Contributor

On Wed, Mar 02, 2016 at 08:48:13AM -0800, Vadim Petrochenkov wrote:

Can the part about OIBITs be omitted at least initially, until the usage experience provide the evidence, that this is really really necessary and unavoidable?

It could, but it is clear that in the longer term we will want to
infer Send bounds. Certainly this is what makes generic combinator
libraries like iterators work smoothly with auto traits (OIBITs) like
send and sync. (Conversly, the fact that trait objects do NOT
permit this sort of "leakage" of auto trait information is a major
pain point with objects.)

To see why this would be limiting for generic combinator libaries,
imagine an example like this:

fn curry<F,U,V,R>(func: F, argument: U) -> @Fn(&V) -> R
    where F: Fn(&U, &V) -> R
{
    move |v| func(&argument, &v)
}

Now I have this nice little combinator that I can use to do stuff like
let foo = curry(Option::unwrap_or, some_opt). But now suppose that
I want to use Rayon to parallelize some computation:

fn my_computation() {
    let foo = curry(Option::unwrap_op, some_opt);
    rayon::join(
        || do_something(foo(22)),
        || do_something_else(foo(22)));
}

Now I get a compilation error because I am sharing foo, and all we
know about foo is that it implements Fn(&V) -> R; we don't
know whether it is Send.

Of course, I can modify curry to require that its inputs are send:

fn curry<F,U,V,R>(func: F, argument: U) -> @Fn(&V) -> R + Send
    where F: Fn(&U, &V) -> R + Send, U: Send
{
    move |v| func(&argument, &v)
}

but now I can't use curry for other cases, where the data is not
sendable. So I need to have curry_send and curry. etc.

That said, there is definitely a need to address this more generally.
For example, you will want to write iterator adapters that are
double-ended if the input is double-ended and so forth. We could
conceivably use the same mechanism to "propagate" Send as we invent
for handling DoubleEndedIterator. But on the other hand, the whole
point of traits like Send and Sync being auto traits is to ensure
that they are, well, automatic, and not something one has to opt in
to.

@nikomatsakis

nikomatsakis Mar 2, 2016

Contributor

On Wed, Mar 02, 2016 at 08:48:13AM -0800, Vadim Petrochenkov wrote:

Can the part about OIBITs be omitted at least initially, until the usage experience provide the evidence, that this is really really necessary and unavoidable?

It could, but it is clear that in the longer term we will want to
infer Send bounds. Certainly this is what makes generic combinator
libraries like iterators work smoothly with auto traits (OIBITs) like
send and sync. (Conversly, the fact that trait objects do NOT
permit this sort of "leakage" of auto trait information is a major
pain point with objects.)

To see why this would be limiting for generic combinator libaries,
imagine an example like this:

fn curry<F,U,V,R>(func: F, argument: U) -> @Fn(&V) -> R
    where F: Fn(&U, &V) -> R
{
    move |v| func(&argument, &v)
}

Now I have this nice little combinator that I can use to do stuff like
let foo = curry(Option::unwrap_or, some_opt). But now suppose that
I want to use Rayon to parallelize some computation:

fn my_computation() {
    let foo = curry(Option::unwrap_op, some_opt);
    rayon::join(
        || do_something(foo(22)),
        || do_something_else(foo(22)));
}

Now I get a compilation error because I am sharing foo, and all we
know about foo is that it implements Fn(&V) -> R; we don't
know whether it is Send.

Of course, I can modify curry to require that its inputs are send:

fn curry<F,U,V,R>(func: F, argument: U) -> @Fn(&V) -> R + Send
    where F: Fn(&U, &V) -> R + Send, U: Send
{
    move |v| func(&argument, &v)
}

but now I can't use curry for other cases, where the data is not
sendable. So I need to have curry_send and curry. etc.

That said, there is definitely a need to address this more generally.
For example, you will want to write iterator adapters that are
double-ended if the input is double-ended and so forth. We could
conceivably use the same mechanism to "propagate" Send as we invent
for handling DoubleEndedIterator. But on the other hand, the whole
point of traits like Send and Sync being auto traits is to ensure
that they are, well, automatic, and not something one has to opt in
to.

This comment has been minimized.

@tikue

tikue Mar 3, 2016

What if there were a way to specify conditional OIBITs? Awful strawman (leaving the sigil, but I'm not necessarily advocating for it):

fn curry<F,U,V,R>(func: F, argument: U) -> @Fn(&V) -> R
    where F: Fn(&U, &V) -> R,
          U: Send + Sync for return
{
    move |v| func(&argument, &v)
}
@tikue

tikue Mar 3, 2016

What if there were a way to specify conditional OIBITs? Awful strawman (leaving the sigil, but I'm not necessarily advocating for it):

fn curry<F,U,V,R>(func: F, argument: U) -> @Fn(&V) -> R
    where F: Fn(&U, &V) -> R,
          U: Send + Sync for return
{
    move |v| func(&argument, &v)
}

This comment has been minimized.

@eddyb

eddyb Mar 3, 2016

Member

What happens when an OIBIT is added, maybe even in a non-std crate?
The thing you're proposing has been considered and it might work if Send and Sync were the only OIBITs ever. But that's wrong, even right now.

@eddyb

eddyb Mar 3, 2016

Member

What happens when an OIBIT is added, maybe even in a non-std crate?
The thing you're proposing has been considered and it might work if Send and Sync were the only OIBITs ever. But that's wrong, even right now.

This comment has been minimized.

@nikomatsakis

nikomatsakis Mar 3, 2016

Contributor

I don't even know what an OIBIT is. Auto trait! Auto trait!

@nikomatsakis

nikomatsakis Mar 3, 2016

Contributor

I don't even know what an OIBIT is. Auto trait! Auto trait!

@bstrie

This comment has been minimized.

Show comment
Hide comment
@bstrie

bstrie Mar 3, 2016

Contributor

A few comments:

  1. Even with the limitations described, would this be sufficient to replace all of the unnecessarily-boxed closures and overspecified iterators in the stdlib? If not, could it replace most of them? Some of them? None of them?

  2. Can you give a concrete example of how this would look with specialization, with code?

  3. Can you elaborate on this quote?

Eventually, we will want to allow the feature to be used within traits, and like in argument position as well (as an ergonomic improvement over today's generics).

It sounds like you're implying that you'd want to turn fn foo<T: Bar>(x: T) into fn foo(x: @Bar), but then you later explicitly disavow this use case, so I'm not quite sure what you're suggesting.

  1. This quote too:

Trait objects already have the issue of explicitly needing to declare Send/Sync-ability, and not extending this problem to abstract return types is desireable. In practice, most uses of this feature would have to add explicit bounds for OIBITS if they wanted to be maximally usable.

Are you implying that in practice this would have the same problem that trait objects have? It seems self-contradictory.

  1. Unanswered question: how will leaky OIBITs interact with incremental recompilation?

  2. Bikeshed time: the RFC repeatedly insists that this is a common feature as a justification for @ over a keyword, but very rarely do I see this feature requested in the wild. Can you provide evidence that this is common? Obviously it's still a necessary feature in order to attain our performance goals, just like specialization, but just like specialization I don't think it's universal enough to warrant maximally-concise and maximally-cryptic syntax (the comparison to & isn't relevant, since references are undoubtedly universal in Rust, and the syntax has precedence from other languages to boot). I also think that it might be a footgun to make it easy to gloss over the presence of an abstract type, given that OIBITs leak. I would much prefer a keyword (I'm ambivalent as to the specific keyword, not married to impl).

  3. Terminology time: I'm still unclear why this warrants the "abstract" term. What about this feature is more abstract than any other feature of the language? Is there precedence from another language to justify it?

Contributor

bstrie commented Mar 3, 2016

A few comments:

  1. Even with the limitations described, would this be sufficient to replace all of the unnecessarily-boxed closures and overspecified iterators in the stdlib? If not, could it replace most of them? Some of them? None of them?

  2. Can you give a concrete example of how this would look with specialization, with code?

  3. Can you elaborate on this quote?

Eventually, we will want to allow the feature to be used within traits, and like in argument position as well (as an ergonomic improvement over today's generics).

It sounds like you're implying that you'd want to turn fn foo<T: Bar>(x: T) into fn foo(x: @Bar), but then you later explicitly disavow this use case, so I'm not quite sure what you're suggesting.

  1. This quote too:

Trait objects already have the issue of explicitly needing to declare Send/Sync-ability, and not extending this problem to abstract return types is desireable. In practice, most uses of this feature would have to add explicit bounds for OIBITS if they wanted to be maximally usable.

Are you implying that in practice this would have the same problem that trait objects have? It seems self-contradictory.

  1. Unanswered question: how will leaky OIBITs interact with incremental recompilation?

  2. Bikeshed time: the RFC repeatedly insists that this is a common feature as a justification for @ over a keyword, but very rarely do I see this feature requested in the wild. Can you provide evidence that this is common? Obviously it's still a necessary feature in order to attain our performance goals, just like specialization, but just like specialization I don't think it's universal enough to warrant maximally-concise and maximally-cryptic syntax (the comparison to & isn't relevant, since references are undoubtedly universal in Rust, and the syntax has precedence from other languages to boot). I also think that it might be a footgun to make it easy to gloss over the presence of an abstract type, given that OIBITs leak. I would much prefer a keyword (I'm ambivalent as to the specific keyword, not married to impl).

  3. Terminology time: I'm still unclear why this warrants the "abstract" term. What about this feature is more abstract than any other feature of the language? Is there precedence from another language to justify it?

@thepowersgang

This comment has been minimized.

Show comment
Hide comment
@thepowersgang

thepowersgang Mar 3, 2016

Contributor

(Bikeshed) I'd like to add another argument against @Trait - Both because it was once a pointer sigil, and because sigils semi-imply pointers (both of the other sigiled types in rust are pointers), it would look like there's indirection.

As for OIBITs - I'm for not inferring them to start with, then see what happens. Even if this feature is stabilised before OIBITs are cleaned up, it's easier to remove the need for explicit annotations than it is to add them.

Contributor

thepowersgang commented Mar 3, 2016

(Bikeshed) I'd like to add another argument against @Trait - Both because it was once a pointer sigil, and because sigils semi-imply pointers (both of the other sigiled types in rust are pointers), it would look like there's indirection.

As for OIBITs - I'm for not inferring them to start with, then see what happens. Even if this feature is stabilised before OIBITs are cleaned up, it's easier to remove the need for explicit annotations than it is to add them.

@tikue

This comment has been minimized.

Show comment
Hide comment
@tikue

tikue Mar 3, 2016

Very excited to be discussing this again! Two comments for now:

  1. I also am strongly against a sigil. I remember Rust pre-2014; I don't want to go back there.
  2. The notion of leaky OIBITs scares me a lot.

This means, however, that it has to be considered a silent breaking change to change a function with a abstract return type in a way that removes OIBIT impls, which might be a problem. (As noted above, this is already the case for struct definitions.)

I'd argue that struct definitions got this wrong, and we shouldn't increase the reach of the problem.

tikue commented Mar 3, 2016

Very excited to be discussing this again! Two comments for now:

  1. I also am strongly against a sigil. I remember Rust pre-2014; I don't want to go back there.
  2. The notion of leaky OIBITs scares me a lot.

This means, however, that it has to be considered a silent breaking change to change a function with a abstract return type in a way that removes OIBIT impls, which might be a problem. (As noted above, this is already the case for struct definitions.)

I'd argue that struct definitions got this wrong, and we shouldn't increase the reach of the problem.

@eddyb

This comment has been minimized.

Show comment
Hide comment
@eddyb

eddyb Mar 3, 2016

Member

@tikue Having actually worked with/on an implementation of impl Trait, I could assure you you'd regret both of those points, sooner or later.

But I can't just expect everyone to take my word for it.

So maybe I should rebase my branch from last year and get this in nightly with no OIBIT leakage and no nice syntax, what do you say?

If people can make use of it in that state, I'll gladly reconsider my opinion.

But until either "side" has seen some proof, it's largely a pointless discussion, I'm afraid.

Member

eddyb commented Mar 3, 2016

@tikue Having actually worked with/on an implementation of impl Trait, I could assure you you'd regret both of those points, sooner or later.

But I can't just expect everyone to take my word for it.

So maybe I should rebase my branch from last year and get this in nightly with no OIBIT leakage and no nice syntax, what do you say?

If people can make use of it in that state, I'll gladly reconsider my opinion.

But until either "side" has seen some proof, it's largely a pointless discussion, I'm afraid.

@eddyb

This comment has been minimized.

Show comment
Hide comment
@eddyb

eddyb Mar 3, 2016

Member

From some old reddit comment: a potential way to "hack in" conditional traits:

// This is done only once.
trait DoubleEndedIf<I: Iterator>: Iterator {
    fn next_back_if(&mut self) -> Option<Self::Item> where I: DoubleEndedIterator;
}
struct MaybeDoubleEnded<T>(T);
impl<T: Iterator> Iterator for MaybeDoubleEnded<T> {
    type Item = T::Item;
    fn next(&mut self) -> Option<T::Item> {
        self.0.next()
    }
}
impl<I: DoubleEndedIterator, T: DoubleEndedIf<I>> DoubleEndedIterator for MaybeDoubleEnded<T> {
    fn next_back(&mut self) -> Option<T::Item> {
        self.0.next_back_if()
    }
}
// You need this for every such maybe-double-ended iterator.
impl<I: Iterator, F: FnMut(&I::Item) -> bool> DoubleEndedIf<I> for Filter<I, F> {
    fn next_back_if(&mut self) -> Option<Self::Item> {
        self.next_back()
    }
}
// And this is how you write your anonymized functions:
fn do_filter<I: Iterator<Item=u8>>(i: I) -> MaybeDoubleEnded<impl DoubleEndedIf<I>> {
    i.filter(|x| x != 0)
}
Member

eddyb commented Mar 3, 2016

From some old reddit comment: a potential way to "hack in" conditional traits:

// This is done only once.
trait DoubleEndedIf<I: Iterator>: Iterator {
    fn next_back_if(&mut self) -> Option<Self::Item> where I: DoubleEndedIterator;
}
struct MaybeDoubleEnded<T>(T);
impl<T: Iterator> Iterator for MaybeDoubleEnded<T> {
    type Item = T::Item;
    fn next(&mut self) -> Option<T::Item> {
        self.0.next()
    }
}
impl<I: DoubleEndedIterator, T: DoubleEndedIf<I>> DoubleEndedIterator for MaybeDoubleEnded<T> {
    fn next_back(&mut self) -> Option<T::Item> {
        self.0.next_back_if()
    }
}
// You need this for every such maybe-double-ended iterator.
impl<I: Iterator, F: FnMut(&I::Item) -> bool> DoubleEndedIf<I> for Filter<I, F> {
    fn next_back_if(&mut self) -> Option<Self::Item> {
        self.next_back()
    }
}
// And this is how you write your anonymized functions:
fn do_filter<I: Iterator<Item=u8>>(i: I) -> MaybeDoubleEnded<impl DoubleEndedIf<I>> {
    i.filter(|x| x != 0)
}
@jdub

This comment has been minimized.

Show comment
Hide comment
@jdub

jdub Mar 3, 2016

Likely a silly question, but what's behind the requirement for a sigil or keyword?

fn foo(n: u32) -> Iterator<u32> {
    (0..n).map(|x| x * 100)
}

jdub commented Mar 3, 2016

Likely a silly question, but what's behind the requirement for a sigil or keyword?

fn foo(n: u32) -> Iterator<u32> {
    (0..n).map(|x| x * 100)
}
@eddyb

This comment has been minimized.

Show comment
Hide comment
@eddyb

eddyb Mar 3, 2016

Member

@bstrie "Abstract" is from some ML-related comments poisoning some of the older discussion IMO.
I've always called this "anonymized return types", because that's what they technically are, types with their original identity forgotten (but still the same type in terms of low-level representation).

Member

eddyb commented Mar 3, 2016

@bstrie "Abstract" is from some ML-related comments poisoning some of the older discussion IMO.
I've always called this "anonymized return types", because that's what they technically are, types with their original identity forgotten (but still the same type in terms of low-level representation).

@diwic

This comment has been minimized.

Show comment
Hide comment
@diwic

diwic Mar 3, 2016

And another silly question :-) How come everybody's using impl? I think type would make more sense, making it look like it is the "associated type" of a function:

fn foo<type I: Iterator<u32>>(n: u32) -> I {
    // This row should be completely inferred in most cases, 
    // but you can also write it out if necessary
    type I = Map<Range<u32>, _>;

    (0..n).map(|x| x * 100)
}

So syntactically, now we have three things which can be written inside <>:

  • P - input type parameter
  • 'P - lifetime parameter
  • type P - output type parameter output type / associated type

This could also allow us to reference the parameter as foo::I in other places, so you could have a struct Bar(foo::I) if you'd like to store the iterator somewhere.

Edit: Fixed nit picking pointed out by @eddyb

diwic commented Mar 3, 2016

And another silly question :-) How come everybody's using impl? I think type would make more sense, making it look like it is the "associated type" of a function:

fn foo<type I: Iterator<u32>>(n: u32) -> I {
    // This row should be completely inferred in most cases, 
    // but you can also write it out if necessary
    type I = Map<Range<u32>, _>;

    (0..n).map(|x| x * 100)
}

So syntactically, now we have three things which can be written inside <>:

  • P - input type parameter
  • 'P - lifetime parameter
  • type P - output type parameter output type / associated type

This could also allow us to reference the parameter as foo::I in other places, so you could have a struct Bar(foo::I) if you'd like to store the iterator somewhere.

Edit: Fixed nit picking pointed out by @eddyb

@eddyb

This comment has been minimized.

Show comment
Hide comment
@eddyb

eddyb Mar 3, 2016

Member

Nit picking: "output type parameter" could be considered nonsensical in contexts where "parameter" implies "input".

Member

eddyb commented Mar 3, 2016

Nit picking: "output type parameter" could be considered nonsensical in contexts where "parameter" implies "input".

@tikue

This comment has been minimized.

Show comment
Hide comment
@tikue

tikue Mar 3, 2016

@eddyb I definitely think we need a better way to specify conditional traits. Your example of manually adding conditionally traits is pretty unergonomic, which is extra unfortunate since this RFC is largely about ergonomics. But like @nikomatsakis pointed out, conditional traits would be useful for things other than OIBITs, so perhaps it merits an RFC of its own.

If there's even a small possibility of Rust gaining support for conditional traits, then I highly encourage the avoidance of leaky OIBITs at this point in time.

tikue commented Mar 3, 2016

@eddyb I definitely think we need a better way to specify conditional traits. Your example of manually adding conditionally traits is pretty unergonomic, which is extra unfortunate since this RFC is largely about ergonomics. But like @nikomatsakis pointed out, conditional traits would be useful for things other than OIBITs, so perhaps it merits an RFC of its own.

If there's even a small possibility of Rust gaining support for conditional traits, then I highly encourage the avoidance of leaky OIBITs at this point in time.

@cramertj

This comment has been minimized.

Show comment
Hide comment
@cramertj

cramertj Jun 28, 2016

Member

@eddyb That is why I like some/any so much. 😄
impl Trait meaning a different thing in argument position than in return position is really odd to me.
The conflicting language with option types is a bummer, though.

Member

cramertj commented Jun 28, 2016

@eddyb That is why I like some/any so much. 😄
impl Trait meaning a different thing in argument position than in return position is really odd to me.
The conflicting language with option types is a bummer, though.

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jul 1, 2016

Contributor

@bstrie @wycats I actually think the question of how to resolve "unresolved questions" has been one where people seem to have a lot of different expectations. When it came up for the ? RFC (about adding option support) the conclusion was that we ought to spell out what procedure in advance, at least -- but maybe we should just settle on saying that once the question is resolved, we should open an amendment RFC that includes the resolution. Not sure. I'm generally wary of requiring more process, but otoh if the decision is not controversial, there's no reason we can't open an RFC and quickly move to FCP, I guess? (This seems to interact with the overall timeline of stabilization as well.) Anyway, clearly this isn't the place for a general discussion about RFC procedures, so I'll can it for now. :)

Contributor

nikomatsakis commented Jul 1, 2016

@bstrie @wycats I actually think the question of how to resolve "unresolved questions" has been one where people seem to have a lot of different expectations. When it came up for the ? RFC (about adding option support) the conclusion was that we ought to spell out what procedure in advance, at least -- but maybe we should just settle on saying that once the question is resolved, we should open an amendment RFC that includes the resolution. Not sure. I'm generally wary of requiring more process, but otoh if the decision is not controversial, there's no reason we can't open an RFC and quickly move to FCP, I guess? (This seems to interact with the overall timeline of stabilization as well.) Anyway, clearly this isn't the place for a general discussion about RFC procedures, so I'll can it for now. :)

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jul 1, 2016

Contributor

@withoutboats This question of gaining experience is a good one, and certainly not limited to this RFC. Getting projects like servo to use it is definitely one thing we can (and have) done. Perhaps we can also try teaching and using the feature 1-on-1 -- I've definitely used tutorials that I run in Boston, for example, as a way to experiment with how readily people adopt different approaches to teaching Rust. I could imagine experimentally integrating impl Trait into that sort of setting to gain (anecdotal) data. (Perhaps also actual teaching settings; there are some university courses on Rust and so forth we could work with.)

Contributor

nikomatsakis commented Jul 1, 2016

@withoutboats This question of gaining experience is a good one, and certainly not limited to this RFC. Getting projects like servo to use it is definitely one thing we can (and have) done. Perhaps we can also try teaching and using the feature 1-on-1 -- I've definitely used tutorials that I run in Boston, for example, as a way to experiment with how readily people adopt different approaches to teaching Rust. I could imagine experimentally integrating impl Trait into that sort of setting to gain (anecdotal) data. (Perhaps also actual teaching settings; there are some university courses on Rust and so forth we could work with.)

@mbrubeck

This comment has been minimized.

Show comment
Hide comment
@mbrubeck

mbrubeck Jul 8, 2016

Contributor

Something went wrong with the merge, and this RFC was not actually added to the repo.

Contributor

mbrubeck commented Jul 8, 2016

Something went wrong with the merge, and this RFC was not actually added to the repo.

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Jul 15, 2016

Member

Added RFC text.

Member

aturon commented Jul 15, 2016

Added RFC text.

@notriddle

This comment has been minimized.

Show comment
Hide comment
@notriddle

notriddle Jul 29, 2016

Contributor

What would the ideal behavior for #[must_use] be?

  1. Would we want it to fire at call site when the trait is marked must use? This has the disadvantage of making it possible to silently drop the type.
  2. Fire when associated with the concrete type? This has the disadvantage of making it impossible to tell from the docs whether a function returns a must-use type, thus triggering the lint.
  3. Fire at the return site when a must use type is returned through impl trait.
  4. Combine 1 and 3, creating a whole chain of custody. Probably ideal, but complicated from an implementation standpoint.
Contributor

notriddle commented Jul 29, 2016

What would the ideal behavior for #[must_use] be?

  1. Would we want it to fire at call site when the trait is marked must use? This has the disadvantage of making it possible to silently drop the type.
  2. Fire when associated with the concrete type? This has the disadvantage of making it impossible to tell from the docs whether a function returns a must-use type, thus triggering the lint.
  3. Fire at the return site when a must use type is returned through impl trait.
  4. Combine 1 and 3, creating a whole chain of custody. Probably ideal, but complicated from an implementation standpoint.
@eddyb

This comment has been minimized.

Show comment
Hide comment
@eddyb

eddyb Jul 29, 2016

Member

@notriddle 5. Does it really matter? #[must_use] is a hint and not required for correctness.
There's no point in warning about using a type that you don't know how you "must" use.
The only solution that makes sense, IMO, if we do anything, is:
6. Allow #[must_use] on the function itself and handle that at direct call sites.

Member

eddyb commented Jul 29, 2016

@notriddle 5. Does it really matter? #[must_use] is a hint and not required for correctness.
There's no point in warning about using a type that you don't know how you "must" use.
The only solution that makes sense, IMO, if we do anything, is:
6. Allow #[must_use] on the function itself and handle that at direct call sites.

@notriddle

This comment has been minimized.

Show comment
Hide comment
@notriddle

notriddle Jul 29, 2016

Contributor

I admit it isn't that important. I just don't want must_use to bitrot into nothing by failing to support anything that wasn't present in 1.0.

Contributor

notriddle commented Jul 29, 2016

I admit it isn't that important. I just don't want must_use to bitrot into nothing by failing to support anything that wasn't present in 1.0.

@eddyb

This comment has been minimized.

Show comment
Hide comment
@eddyb

eddyb Jul 29, 2016

Member

@notriddle Once we actually use impl Trait with a #[must_use] type somewhere, this could be revisited, but I can't think of anything right now.

Member

eddyb commented Jul 29, 2016

@notriddle Once we actually use impl Trait with a #[must_use] type somewhere, this could be revisited, but I can't think of anything right now.

@norru

This comment has been minimized.

Show comment
Hide comment
@norru

norru Sep 2, 2016

Silly question. Is there a roadmap for the release of this feature to the Beta channel? At the moment this is at the top of my "must have next" - mainly because of the well known "monster iterator types" ergonomics problem.

norru commented Sep 2, 2016

Silly question. Is there a roadmap for the release of this feature to the Beta channel? At the moment this is at the top of my "must have next" - mainly because of the well known "monster iterator types" ergonomics problem.

@thepowersgang

This comment has been minimized.

Show comment
Hide comment
@thepowersgang

thepowersgang Sep 2, 2016

Contributor

@norru - Tracking issue rust-lang/rust#34511

Contributor

thepowersgang commented Sep 2, 2016

@norru - Tracking issue rust-lang/rust#34511

@steveklabnik

This comment has been minimized.

Show comment
Hide comment
@steveklabnik

steveklabnik Sep 2, 2016

Member

@norru also worth noting, last I heard, it was unclear if changing Iterator
to use this syntax is a breaking change or not. In the most straightforward
sense, it is, so that's some work that needs to be done... so even if it
does land, it might not fix that problem.

On Fri, Sep 2, 2016 at 7:24 AM, John Hodge (Mutabah) <
notifications@github.com> wrote:

@norru https://github.com/norru - Tracking issue rust-lang/rust#34511
rust-lang/rust#34511


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#1522 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AABsis8Sc87QWHWM6DVIFSmaMl4T3P2Oks5qmAd2gaJpZM4HmxvM
.

Member

steveklabnik commented Sep 2, 2016

@norru also worth noting, last I heard, it was unclear if changing Iterator
to use this syntax is a breaking change or not. In the most straightforward
sense, it is, so that's some work that needs to be done... so even if it
does land, it might not fix that problem.

On Fri, Sep 2, 2016 at 7:24 AM, John Hodge (Mutabah) <
notifications@github.com> wrote:

@norru https://github.com/norru - Tracking issue rust-lang/rust#34511
rust-lang/rust#34511


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#1522 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AABsis8Sc87QWHWM6DVIFSmaMl4T3P2Oks5qmAd2gaJpZM4HmxvM
.

@norru

This comment has been minimized.

Show comment
Hide comment
@norru

norru Sep 2, 2016

@steveklabnik It might cover my use case anyway. I'll give it a go as soon as it's available in the beta channel and then we'll see. Cheers!

norru commented Sep 2, 2016

@steveklabnik It might cover my use case anyway. I'll give it a go as soon as it's available in the beta channel and then we'll see. Cheers!

@steveklabnik

This comment has been minimized.

Show comment
Hide comment
@steveklabnik

steveklabnik Sep 2, 2016

Member

Once it's in beta, it's already on its way to stable. If you really want to make sure it works for you, you should try it out on nightly, so that way you can point out problems before it's set to go stable.

Member

steveklabnik commented Sep 2, 2016

Once it's in beta, it's already on its way to stable. If you really want to make sure it works for you, you should try it out on nightly, so that way you can point out problems before it's set to go stable.

@norru

This comment has been minimized.

Show comment
Hide comment
@norru

norru Sep 2, 2016

It makes sense - it's already in there, is it not? EDIT: just got nightly but no luck - I feel I'm missing something silly here.

nico@nico-700G7A:~/Projects/rust-oids$ rustup run nightly rustc -V
rustc 1.13.0-nightly (497d67d70 2016-09-01)
nico@nico-700G7A:~/Projects/rust-oids$ rustup run nightly rustc src/main.rs
error: expected type, found keyword `trait`
   --> src/backend/world/swarm.rs:100:31
    |
100 |   pub fn agents_iter(&self) -> trait Iterator<Item=&Agent> {
    |                                ^^^^^

error: aborting due to previous error

norru commented Sep 2, 2016

It makes sense - it's already in there, is it not? EDIT: just got nightly but no luck - I feel I'm missing something silly here.

nico@nico-700G7A:~/Projects/rust-oids$ rustup run nightly rustc -V
rustc 1.13.0-nightly (497d67d70 2016-09-01)
nico@nico-700G7A:~/Projects/rust-oids$ rustup run nightly rustc src/main.rs
error: expected type, found keyword `trait`
   --> src/backend/world/swarm.rs:100:31
    |
100 |   pub fn agents_iter(&self) -> trait Iterator<Item=&Agent> {
    |                                ^^^^^

error: aborting due to previous error
@eddyb

This comment has been minimized.

Show comment
Hide comment
@eddyb

eddyb Sep 2, 2016

Member

@norru The syntax is impl Trait - that is, impl is a keyword and Trait is any trait path.
Not sure where you got trait Trait from, I didn't see that in the RFC or comments (I hope there's no misleading information out there, this early, it would be a shame).

Member

eddyb commented Sep 2, 2016

@norru The syntax is impl Trait - that is, impl is a keyword and Trait is any trait path.
Not sure where you got trait Trait from, I didn't see that in the RFC or comments (I hope there's no misleading information out there, this early, it would be a shame).

@norru

This comment has been minimized.

Show comment
Hide comment
@norru

norru Sep 4, 2016

@eddyb Ace, yes I got the syntax wrong for no reason except me being dense :).
Thanks.

norru commented Sep 4, 2016

@eddyb Ace, yes I got the syntax wrong for no reason except me being dense :).
Thanks.

@norru

This comment has been minimized.

Show comment
Hide comment
@norru

norru Sep 4, 2016

nico@nico-700G7A:~/Projects/rust-oids$ rustup run nightly cargo run --release
...
  Compiling rust-oids v0.2.0 (file:///home/nico/Projects/rust-oids)
...
rustc: /buildslave/rust-buildbot/slave/nightly-dist-rustc-linux/build/src/llvm/lib/IR/Instructions.cpp:263: void llvm::CallInst::init(llvm::FunctionType*, llvm::Value*, llvm::ArrayRef<llvm::Value*>, llvm::ArrayRef<llvm::OperandBundleDefT<llvm::Value*> >, const llvm::Twine&): Assertion `(i >= FTy->getNumParams() || FTy->getParamType(i) == Args[i]->getType()) && "Calling a function with a bad signature!"' failed.

How do I report it to the compiler team?

norru commented Sep 4, 2016

nico@nico-700G7A:~/Projects/rust-oids$ rustup run nightly cargo run --release
...
  Compiling rust-oids v0.2.0 (file:///home/nico/Projects/rust-oids)
...
rustc: /buildslave/rust-buildbot/slave/nightly-dist-rustc-linux/build/src/llvm/lib/IR/Instructions.cpp:263: void llvm::CallInst::init(llvm::FunctionType*, llvm::Value*, llvm::ArrayRef<llvm::Value*>, llvm::ArrayRef<llvm::OperandBundleDefT<llvm::Value*> >, const llvm::Twine&): Assertion `(i >= FTy->getNumParams() || FTy->getParamType(i) == Args[i]->getType()) && "Calling a function with a bad signature!"' failed.

How do I report it to the compiler team?

@eddyb

This comment has been minimized.

Show comment
Hide comment
@eddyb

eddyb Sep 4, 2016

Member

@norru You should open an issue on https://github.com/rust-lang/rust.

Member

eddyb commented Sep 4, 2016

@norru You should open an issue on https://github.com/rust-lang/rust.

@gurry

This comment has been minimized.

Show comment
Hide comment
@gurry

gurry Feb 15, 2017

May be a dumb question, but what if Rust allowed using a trait any place where you can use a concrete type today. In a way, it'll mean traits will (syntactically) behave akin to interfaces in mainstream languages. Not only will this resolve the questions posed here (i.e. whether to use impl or not) it will also make Rust easier to learn for new people.

What I mean is, if you can do this today:

struct MyType1;
struct MyType2;

fn myfunc(x: MyType1) -> MyType2 {}

it should also be possible to do this:

trait MyTrait1 {}
trait MyTrait2 {}

fn myfunc(x: MyTrait1) -> MyTrait2 {}

My feeling about the proposed new use of impl is that it is making the generics ever harder to grok for newbies by introducing one more concept to an already extravagant syntax (e.g. things like how lifetime annotations look similar to traits and you have two ways to specify trait constaints, inline and where clause). This will only add to the existing complexity.

gurry commented Feb 15, 2017

May be a dumb question, but what if Rust allowed using a trait any place where you can use a concrete type today. In a way, it'll mean traits will (syntactically) behave akin to interfaces in mainstream languages. Not only will this resolve the questions posed here (i.e. whether to use impl or not) it will also make Rust easier to learn for new people.

What I mean is, if you can do this today:

struct MyType1;
struct MyType2;

fn myfunc(x: MyType1) -> MyType2 {}

it should also be possible to do this:

trait MyTrait1 {}
trait MyTrait2 {}

fn myfunc(x: MyTrait1) -> MyTrait2 {}

My feeling about the proposed new use of impl is that it is making the generics ever harder to grok for newbies by introducing one more concept to an already extravagant syntax (e.g. things like how lifetime annotations look similar to traits and you have two ways to specify trait constaints, inline and where clause). This will only add to the existing complexity.

@oli-obk

This comment has been minimized.

Show comment
Hide comment
@oli-obk

oli-obk Feb 15, 2017

Contributor

@gurry: unfortunately there's an issue when you have the type Box<MyTrait>, because right now that is a fat pointer (pointer to value + pointer to vtable), but with the suggested change, this would be Box<TypeThatImplementsMyTrait>, and that's a breaking change.

These ambiguities were heavily discussed in #1603

Contributor

oli-obk commented Feb 15, 2017

@gurry: unfortunately there's an issue when you have the type Box<MyTrait>, because right now that is a fat pointer (pointer to value + pointer to vtable), but with the suggested change, this would be Box<TypeThatImplementsMyTrait>, and that's a breaking change.

These ambiguities were heavily discussed in #1603

@withoutboats

This comment has been minimized.

Show comment
Hide comment
@withoutboats

withoutboats Feb 15, 2017

Contributor

@gurry Everyone would prefer if we could just say "you can use a trait any place where you can use a type," but the problem is what it means to do that. There are three obvious semantics (generics, dynamic dispatch, and the semantics in this proposal), all of which the language now supports with different syntaxes. None of them are redundant with the others.

Contributor

withoutboats commented Feb 15, 2017

@gurry Everyone would prefer if we could just say "you can use a trait any place where you can use a type," but the problem is what it means to do that. There are three obvious semantics (generics, dynamic dispatch, and the semantics in this proposal), all of which the language now supports with different syntaxes. None of them are redundant with the others.

@gnzlbg

This comment has been minimized.

Show comment
Hide comment
@gnzlbg

gnzlbg Feb 15, 2017

Contributor

@withoutboats I think that ideally, existential types and generics, which both use static dispatch, should have the same syntax, and trait objects, which uses dynamic dispatch, would have a different syntax.

Since that ship has sailed, I really like that this RFC introduces special syntax, impl Trait, for existential types. It does leave Rust in a bit of a weird spot where trait objects that use dynamic dispatch shares the syntax with generics which uses static dispatch, and the outlier is existential types which also uses static dispatch.

But this is just that: a weird spot in the design space. It could be improved by encouraging users to use a special syntax for dynamic dispatch in the future (in another RFC), which would leave Rust with 3 different syntaxes to denote different things (generics, existential, and dynamic dispatch).

Maybe that would not be that weird, but whether this is worth pursuing or not is kind of orthogonal to whether this RFC is stabilized as is. We have painted ourselves into a corner here, and as I see it, all the ways out that do not break the language involve some special syntax for impl Trait.

Contributor

gnzlbg commented Feb 15, 2017

@withoutboats I think that ideally, existential types and generics, which both use static dispatch, should have the same syntax, and trait objects, which uses dynamic dispatch, would have a different syntax.

Since that ship has sailed, I really like that this RFC introduces special syntax, impl Trait, for existential types. It does leave Rust in a bit of a weird spot where trait objects that use dynamic dispatch shares the syntax with generics which uses static dispatch, and the outlier is existential types which also uses static dispatch.

But this is just that: a weird spot in the design space. It could be improved by encouraging users to use a special syntax for dynamic dispatch in the future (in another RFC), which would leave Rust with 3 different syntaxes to denote different things (generics, existential, and dynamic dispatch).

Maybe that would not be that weird, but whether this is worth pursuing or not is kind of orthogonal to whether this RFC is stabilized as is. We have painted ourselves into a corner here, and as I see it, all the ways out that do not break the language involve some special syntax for impl Trait.

@withoutboats

This comment has been minimized.

Show comment
Hide comment
@withoutboats

withoutboats Feb 15, 2017

Contributor

@gnzlbg at minimum there would need to be a distinct elaborated syntax for univerals/existentials; we can't tell which you want without examining the function body which would mean global type inference

Contributor

withoutboats commented Feb 15, 2017

@gnzlbg at minimum there would need to be a distinct elaborated syntax for univerals/existentials; we can't tell which you want without examining the function body which would mean global type inference

@vi

This comment has been minimized.

Show comment
Hide comment
@vi

vi Feb 15, 2017

Was the fn generic(...) -> <Iterator> {...} syntax from the "Rust easier for beginners" thread already discussed here?

vi commented Feb 15, 2017

Was the fn generic(...) -> <Iterator> {...} syntax from the "Rust easier for beginners" thread already discussed here?

@gurry

This comment has been minimized.

Show comment
Hide comment
@gurry

gurry Feb 16, 2017

Thanks guys. Just read the whole of #1603. I appreciate the whole picture a lot better now.

gurry commented Feb 16, 2017

Thanks guys. Just read the whole of #1603. I appreciate the whole picture a lot better now.

@bestouff

This comment has been minimized.

Show comment
Hide comment
@bestouff

bestouff Jun 2, 2017

Contributor

Will it be possible to use impl Trait with let ? Say:

let a impl MyTrait = value;

That would be way cleaner and readable IMHO.

Contributor

bestouff commented Jun 2, 2017

Will it be possible to use impl Trait with let ? Say:

let a impl MyTrait = value;

That would be way cleaner and readable IMHO.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment