Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upRFC: Add unboxed, abstract return types #105
Conversation
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
aturon
Jun 3, 2014
Member
FWIW, I feel somewhat uneasy about the impl Trait syntax proposed here; bikeshedding welcome!
I am currently leaning toward the more conservative design detailed under "Alternatives".
|
FWIW, I feel somewhat uneasy about the I am currently leaning toward the more conservative design detailed under "Alternatives". |
huonw
reviewed
Jun 3, 2014
0000-abstract-return-types.md
| In today's Rust, you can write a function signature like | ||
| ````rust | ||
| fn consume_iter_static<I: Iterator<u8>>(iter: I) | ||
| fn consume_iter_dyanmic(iter: Box<Iterator<u8>>) |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
cmr
reviewed
Jun 3, 2014
| out, which can be very painful. Unboxed abstract types only require writing the | ||
| trait bound. | ||
| * _Documentation_. In today's Rust, reading the documentation for the `Iterator` |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
huonw
reviewed
Jun 3, 2014
| This code is roughly equivalent to | ||
| ````rust | ||
| pub struct Result_produce_iter_static( |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
huonw
Jun 3, 2014
Member
Totally minor point, but I think this example would be clearer as just
struct Result_produce_iter_static {
inner: iter::Skip<...>
}
impl Iterator<int> for Result_produce_iter_static {
fn next(&mut self) -> Option<int> { self.inner.next() }
}(In particular, there's no runtime difference for using a so-called "newtype" vs a normal struct, unlike Haskell; and I've noticed that LLVM seems to optimise a plain struct better than tuple structs anyway.)
huonw
Jun 3, 2014
Member
Totally minor point, but I think this example would be clearer as just
struct Result_produce_iter_static {
inner: iter::Skip<...>
}
impl Iterator<int> for Result_produce_iter_static {
fn next(&mut self) -> Option<int> { self.inner.next() }
}(In particular, there's no runtime difference for using a so-called "newtype" vs a normal struct, unlike Haskell; and I've noticed that LLVM seems to optimise a plain struct better than tuple structs anyway.)
huonw
reviewed
Jun 3, 2014
0000-abstract-return-types.md
| Just as is currently done for trait objects, the typechecker must ensure that | ||
| lifetime parameters are not stripped when using an unboxed abstract type. | ||
| For example (adapted from @glaebhoeri): |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
nrc
Jun 3, 2014
Member
Could you explain how this (e.g., fn foo() -> Tr in you proposal, where Tr is a trait) is different from, e.g., fn foo<X: T>()-> X in current Rust. As far as I can see, your proposal allows the callee to specify the concrete type for X rather than the caller - are there other differences? Is there a benefit over using a default type parameter for X (other than better encapsulation)?
Am I correct in assuming that the encapsulation here is only at the programmer level? That is, from the compiler's point of view, the caller does know the concrete type?
|
Could you explain how this (e.g., Am I correct in assuming that the encapsulation here is only at the programmer level? That is, from the compiler's point of view, the caller does know the concrete type? |
nrc
reviewed
Jun 3, 2014
| _implicit_ type argument. | ||
| Using unboxed abstract types in arguments makes (simple) static and dynamic | ||
| dispatch syntactically closer: |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
nrc
Jun 3, 2014
Member
I fear that this is a downside - the difference at the moment is relatively easy to explain. With this shorthand, I fear the syntax for static and dynamic dispatch is too similar. In other words, we break the principle of 'things which are different should look different'.
nrc
Jun 3, 2014
Member
I fear that this is a downside - the difference at the moment is relatively easy to explain. With this shorthand, I fear the syntax for static and dynamic dispatch is too similar. In other words, we break the principle of 'things which are different should look different'.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
chris-morgan
Jun 4, 2014
Member
At present, the static form is clumsy to read or to write, and so many people go in the direction of the less efficient dynamic dispatch. I view the increase in similarity as an improvement.
chris-morgan
Jun 4, 2014
Member
At present, the static form is clumsy to read or to write, and so many people go in the direction of the less efficient dynamic dispatch. I view the increase in similarity as an improvement.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
bstrie
Jun 4, 2014
Contributor
I would like to know where you've observed people preferring dynamic dispatch just because of the syntax. The static dispatch syntax is more familiar to C++ programmers, and I'd expect them to reach for it first.
bstrie
Jun 4, 2014
Contributor
I would like to know where you've observed people preferring dynamic dispatch just because of the syntax. The static dispatch syntax is more familiar to C++ programmers, and I'd expect them to reach for it first.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
sfackler
Jun 4, 2014
Member
I've seen people with functions returning Box<Iterator> in IRC decently often. The only alternative right now is to write a type signature that's probably too complex for anyone new to Rust to even figure out:
pub struct PhfMapEntries<'a, T> {
priv iter: iter::FilterMap<'a,
&'a Option<(&'static str, T)>,
(&'static str, &'a T),
slice::Items<'a, Option<(&'static str, T)>>>,
}
sfackler
Jun 4, 2014
Member
I've seen people with functions returning Box<Iterator> in IRC decently often. The only alternative right now is to write a type signature that's probably too complex for anyone new to Rust to even figure out:
pub struct PhfMapEntries<'a, T> {
priv iter: iter::FilterMap<'a,
&'a Option<(&'static str, T)>,
(&'static str, &'a T),
slice::Items<'a, Option<(&'static str, T)>>>,
}
nrc
reviewed
Jun 3, 2014
0000-abstract-return-types.md
| fn extend_dynamic(&mut self, iterator: &Iterator<T>) | ||
| ```` | ||
| It may be especially important for passing unboxed closures as arguments. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
aturon
Jun 3, 2014
Member
fn foo<X: T>() -> X says "for any type X implementing T, I'll produce an X".
fn foo() -> impl T says "there's some hidden type X implementing T; I'll produce an X"
You cannot use generic types/default type parameters to get at the second meaning, because the point is that the function's code produces a single, concrete return type of its choosing.
From the compiler's point of view, what the caller knows depends on the stage of the compiler:
- during typechecking, the caller knows only the trait bound, not the concrete type
- during codegen, the caller knows the concrete type and generates statically-dispatched calls, etc.
This and other details are, I believe, covered in the RFC; let me know if it's not clear.
|
You cannot use generic types/default type parameters to get at the second meaning, because the point is that the function's code produces a single, concrete return type of its choosing. From the compiler's point of view, what the caller knows depends on the stage of the compiler:
This and other details are, I believe, covered in the RFC; let me know if it's not clear. |
huonw
reviewed
Jun 3, 2014
| could provide different concrete iterator types for the first and second | ||
| components of the tuple. | ||
| ### Structs and other compound types |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
huonw
Jun 3, 2014
Member
Is there a concrete use-case for this? It seems rather more complicated and adds an entirely implicit place of monomorphisation, that is, writing
fn use_foo(f: Foo) {}is actually a generic function and will create multiple instantiations in the binary (am I interpreting this correctly?), but there is absolutely no indication of this from the signature. Is it crazy to restrict it to something like
struct Foo<T: Set<u8>> {
s: T
}
fn use_foo(f: Foo<impl Set<u8>>)(I guess this means not special-casing these types particularly.)
huonw
Jun 3, 2014
Member
Is there a concrete use-case for this? It seems rather more complicated and adds an entirely implicit place of monomorphisation, that is, writing
fn use_foo(f: Foo) {}is actually a generic function and will create multiple instantiations in the binary (am I interpreting this correctly?), but there is absolutely no indication of this from the signature. Is it crazy to restrict it to something like
struct Foo<T: Set<u8>> {
s: T
}
fn use_foo(f: Foo<impl Set<u8>>)(I guess this means not special-casing these types particularly.)
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
aturon
Jun 3, 2014
Member
@huonw I agree; I am uneasy about putting these in structs, and I don't have a strong use-case for it.
The main reason for including it in the proposal was to treat impl Trait consistently as something you can write anywhere a type goes. But the more I think about it, the more I like the conservative alternative I outline in the end: restricting this RFC to function return types, and using the syntax "_ : Trait" instead.
aturon
Jun 3, 2014
Member
@huonw I agree; I am uneasy about putting these in structs, and I don't have a strong use-case for it.
The main reason for including it in the proposal was to treat impl Trait consistently as something you can write anywhere a type goes. But the more I think about it, the more I like the conservative alternative I outline in the end: restricting this RFC to function return types, and using the syntax "_ : Trait" instead.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
aturon
Jun 3, 2014
Member
@nikomatsakis might want to jump in here -- he first suggested allowing impl Trait in structs, but I'm not sure if he had a concrete use-case in mind.
aturon
Jun 3, 2014
Member
@nikomatsakis might want to jump in here -- he first suggested allowing impl Trait in structs, but I'm not sure if he had a concrete use-case in mind.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
nikomatsakis
Jun 4, 2014
Contributor
Not really. I think I was just pushing the idea to see how far it could go. The return value variation is interesting, though I think there is value in permitting it in argument position. We have precedent for having fn signatures have rich shorthands and I think it's served us fairly well.
nikomatsakis
Jun 4, 2014
Contributor
Not really. I think I was just pushing the idea to see how far it could go. The return value variation is interesting, though I think there is value in permitting it in argument position. We have precedent for having fn signatures have rich shorthands and I think it's served us fairly well.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
SiegeLord
Jun 3, 2014
While I really like the idea of using impl Foo in places other than the return type (as removes the syntactic weight of the 'prefered' method of dispatch), the implicit parametrization in the struct example just rubs me the wrong way; is there no way to indicate to the reader what is happening there? In the case of function arguments, this also seems to preclude being able to specify type hints for these implicit type parameters. Or, would this work?
fn foo1(b: impl Foo) {}
foo1<Bar>()
fn foo2<T>(a: T, b: impl Foo) {}
foo2<Bar, Baz>() // T is set to Bar, implicit one is set to BazAlso, just for complete clarity, does the & go before or after the impl?
SiegeLord
commented
Jun 3, 2014
|
While I really like the idea of using fn foo1(b: impl Foo) {}
foo1<Bar>()
fn foo2<T>(a: T, b: impl Foo) {}
foo2<Bar, Baz>() // T is set to Bar, implicit one is set to BazAlso, just for complete clarity, does the |
pczarn
reviewed
Jun 3, 2014
0000-abstract-return-types.md
| # Summary | ||
| Allow functions to return types to return _unboxed abstract types_, written |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
aturon
Jun 3, 2014
Member
Thanks for the quick feedback; I've updated the RFC to respond to most of the points made. I'll also respond in comments.
|
Thanks for the quick feedback; I've updated the RFC to respond to most of the points made. I'll also respond in comments. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
aturon
Jun 3, 2014
Member
Added notes to the RFC on an additional choice: allowing impl Trait only in function signatures.
|
Added notes to the RFC on an additional choice: allowing |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
aturon
Jun 3, 2014
Member
@SiegeLord I very much agree with your concerns about allowing impl Trait to used everywhere, especially for the struct example. Honestly, I think allowing impl Trait in struct fields is probably a bad idea :-)
On the other hand, I've added an alternative design where impl Trait is only permitted in function signatures, which keeps the lightweight syntax but also means you can tell exactly where monomorphization is happening. Probably that design could allow explicitly supplying the concrete types for an impl Trait as well (as you're proposing).
Finally, regarding &, I think you'd want & impl Trait for "A reference to some T where T: Trait".
|
@SiegeLord I very much agree with your concerns about allowing On the other hand, I've added an alternative design where Finally, regarding |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
huonw
Jun 3, 2014
Member
I don't see any mention of multiple traits, e.g.
fn foo() -> impl Iterator<int> + CloneIt's probably worth mentioning even if it's not explicitly part of this RFC.
|
I don't see any mention of multiple traits, e.g. fn foo() -> impl Iterator<int> + CloneIt's probably worth mentioning even if it's not explicitly part of this RFC. |
huonw
reviewed
Jun 3, 2014
| fn collect_to_set<T, I: Iterator<T>>(iter: I) -> impl Set<T> | ||
| ```` | ||
| we could allow naming the concrete result type by a path like | ||
| `collect_to_set::<T, I>::impl`. The only way to get a value of this type is by |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
huonw
Jun 3, 2014
Member
On first glance, I like this idea, especially since it makes the equality/self thing fall out automatically.
huonw
Jun 3, 2014
Member
On first glance, I like this idea, especially since it makes the equality/self thing fall out automatically.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
huonw
Jun 4, 2014
Member
Although, there's a slight complication, what about something like
fn nested() -> Vec<impl Foo>There is extra structure here, so presumably the nested::impl type would preferably point to the interior of the Vec rather than the whole return type (i.e. it's returning Vec<nested::impl>, meaning one might wish to write something like let x: &nested::impl = nested().get(0)), which then makes it hard to refer to values with multiple abstract generics, e.g.
fn tuple() -> (impl Iterator<int>, impl Iterator<u8>)Also, what about abstract generics nested in others:
fn nested2() -> impl Iterator<impl Foo>
huonw
Jun 4, 2014
Member
Although, there's a slight complication, what about something like
fn nested() -> Vec<impl Foo>There is extra structure here, so presumably the nested::impl type would preferably point to the interior of the Vec rather than the whole return type (i.e. it's returning Vec<nested::impl>, meaning one might wish to write something like let x: &nested::impl = nested().get(0)), which then makes it hard to refer to values with multiple abstract generics, e.g.
fn tuple() -> (impl Iterator<int>, impl Iterator<u8>)Also, what about abstract generics nested in others:
fn nested2() -> impl Iterator<impl Foo>
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
aturon
Jun 3, 2014
Member
@huonw Just added a brief section in "Unresolved questions" on multiple bounds. I think it should definitely be part of the design, but I'm not sure about the syntax. I seem to recall some problems recently regarding + when not inside < > braces?
Anyway, if it can work, my preferred syntax would be impl Trait1 + Trait2 as you proposed.
|
@huonw Just added a brief section in "Unresolved questions" on multiple bounds. I think it should definitely be part of the design, but I'm not sure about the syntax. I seem to recall some problems recently regarding Anyway, if it can work, my preferred syntax would be |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
huonw
Jun 3, 2014
Member
I seem to recall some problems recently regarding + when not inside < > braces?
I think this was with as, something like
foo as X + Yis ambiguous as (foo as X) + Y or foo as (X+Y).
Which brings us onto another thing, would/should/could explicit some_value as impl Iterator<int> casts be useful?
I think this was with foo as X + Yis ambiguous as Which brings us onto another thing, would/should/could explicit |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
aturon
Jun 3, 2014
Member
@huonw OK, so + for multiple bounds syntax would likely be a problem only if we allow these casts.
I can't offhand see why you'd need such a cast form. I suppose that the RFC implicitly assumes that T can be used for impl Trait whenever T: Trait, without any explicit casts. (Essentially the same behavior you get when instantiating generics today.)
|
@huonw OK, so I can't offhand see why you'd need such a cast form. I suppose that the RFC implicitly assumes that |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
nikomatsakis
Jun 4, 2014
Contributor
I think the meaning of foo as impl Bar would be pretty much the same as using impl in a local variable: essentially an assertion that the type does implement Bar. Not sure if there is much point, though, it could never help your program compile in particular, just make it fail.
|
I think the meaning of |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
nikomatsakis
Jun 4, 2014
Contributor
(Also, the notation impl Foo + Bar is no more of a problem than the Foo + Bar types are today.)
|
(Also, the notation |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
huonw
Jun 4, 2014
Member
(Also, the notation impl Foo + Bar is no more of a problem than the Foo + Bar types are today.)
Which Foo + Bar types?
Which |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
eddyb
Jun 4, 2014
Member
Is there any specific reason the impl keyword is required?
I don't think we'll have a way to return unsized types (because of the technical challenges of doing so, given existing calling conventions), so why not use just the trait name?
That would allow us to write:
fn add(x: int) -> |int| -> int {|y| x + y}|
Is there any specific reason the That would allow us to write: fn add(x: int) -> |int| -> int {|y| x + y} |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
thestinger
Jun 4, 2014
@eddyb: That's what I suggested in my unboxed closure proposal, and I don't see a problem with doing it like that.
thestinger
commented
Jun 4, 2014
|
@eddyb: That's what I suggested in my unboxed closure proposal, and I don't see a problem with doing it like that. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
huonw
Jun 4, 2014
Member
Taking an anonymous generic as a parameter would be ambiguous with trait objects, e.g.
fn foo(x: &mut Trait) { ... }could either be a trait object or equivalent to fn foo<T: Trait>(x: &mut T) { ... }.
|
Taking an anonymous generic as a parameter would be ambiguous with trait objects, e.g. fn foo(x: &mut Trait) { ... }could either be a trait object or equivalent to |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
eddyb
Jun 4, 2014
Member
I was talking about the return types and maybe argument types that aren't behind a pointer, but I see your point.
|
I was talking about the return types and maybe argument types that aren't behind a pointer, but I see your point. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Kimundi
Jun 4, 2014
Member
How would returning a annonymous type that might implement a trait work? For example, iter.map() returns a Map that only implements Iterator if T does, and only implements DoubleEndedIterator if T does.
So, depending on what iterator map() got called on, it would have to either return impl Iterator or impl Iterator+DoubleEndedIterator
|
How would returning a annonymous type that might implement a trait work? For example, iter.map() returns a Map that only implements Iterator if T does, and only implements DoubleEndedIterator if T does. So, depending on what iterator map() got called on, it would have to either return impl Iterator or impl Iterator+DoubleEndedIterator |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
thestinger
Jun 4, 2014
@Kimundi: It would work exactly like bounded type parameters. If you specify it as returning Iterator, then the caller can only use the methods provided by Iterator.
So, depending on what iterator map() got called on, it would have to either return impl Iterator or impl Iterator+DoubleEndedIterator
This isn't really related to the proposal here. It's not possible to do this with a boxed trait object either. The purpose of this proposal is not to replace the existing generics system used for functions like map. It's an extension of trait objects to eliminate unnecessary boxing.
thestinger
commented
Jun 4, 2014
|
@Kimundi: It would work exactly like bounded type parameters. If you specify it as returning
This isn't really related to the proposal here. It's not possible to do this with a boxed trait object either. The purpose of this proposal is not to replace the existing generics system used for functions like |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Kimundi
Jun 4, 2014
Member
Right, its not directly relevant to the intention of this proposal.
But the proposal was talking about hiding complex iterator types behind a impl Iterator, which is restricting in many situations because you lose the double endedness in many generic cases, so I wondered if there was any though to that?
|
Right, its not directly relevant to the intention of this proposal. But the proposal was talking about hiding complex iterator types behind a |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
thestinger
Jun 4, 2014
If you to return an iterator, you need to specify the interface for the caller. In Rust's type system, that means a choice between it being an Iterator and DoubleEndedIterator because it's not possible to do type metaprogramming. Rust's type system does not allow the caller to make any assumptions based on the body of the callee either, and that's a desirable property.
thestinger
commented
Jun 4, 2014
|
If you to return an iterator, you need to specify the interface for the caller. In Rust's type system, that means a choice between it being an |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
pnkfelix
Jun 4, 2014
Member
I'm in favor of adding this in some form. (And I think I like the as-described fn foo(q: impl QuuxTrait) -> impl WidgetTrait syntax.)
@aturon I'm not sure if you addressed this question from @SiegeLord
In the case of function arguments, this also seems to preclude being able to specify type hints for
these implicit type parameters. Or, would this work?fn foo1(b: impl Foo) {} foo1<Bar>() fn foo2<T>(a: T, b: impl Foo) {} foo2::<Bar, Baz>() // T is set to Bar, implicit one is set to Baz
To elaborate: If we attempt to adopt the optional extension to allow these unboxed-abstract trait instances in function argument positions, in the RFC you give examples of how that would desugar to a type-parameterized function definition. But it is not clear to me whether that desugaring is meant to be interpreted literally, in that we would somehow combine the explicit type parameters with the implicitly-injected type parameters.
(The latter sounds easy on the surface, though I do worry about details like in what order multiple impl Trait injected parameters would be added.)
It would greatly benefit the RFC if you added an example of a function signature that had both an impl Trait as one of its arguments and also had explicit type parameters, and showed what it would look like to call such a function (with the ::<..>(...) syntax for explicitly instantiating the generic parameters).
|
I'm in favor of adding this in some form. (And I think I like the as-described @aturon I'm not sure if you addressed this question from @SiegeLord
To elaborate: If we attempt to adopt the optional extension to allow these unboxed-abstract trait instances in function argument positions, in the RFC you give examples of how that would desugar to a type-parameterized function definition. But it is not clear to me whether that desugaring is meant to be interpreted literally, in that we would somehow combine the explicit type parameters with the implicitly-injected type parameters. (The latter sounds easy on the surface, though I do worry about details like in what order multiple It would greatly benefit the RFC if you added an example of a function signature that had both an |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
glaebhoerl
Jun 4, 2014
Contributor
My opinion prior to reading this RFC was that it would be best to take the trait object analogy and run with it: make the analogy precise both (a) in syntax and (b) in semantics, with the exception of (c) some unavoidable semantic restrictions on the producer of an unboxed trait object relative to the producer of a boxed trait object.
(a) Syntax: Given that a boxed type is e.g. Box<Type>, an unboxed type is Type, and a boxed trait object is Box<Trait>, an unboxed trait object should be Trait.
(b) Semantics: Box<Trait> behaves like exists T: Trait. Box<T>, so Trait should behave like exists T: Trait. T. In particular, this means that we should not do the "type equality and Self" part of this proposal, because that would break the analogy: it would behave like a hidden compiler-generated newtype rather than like a true existential type, unlike boxed trait objects. Just as a boxed trait object in argument position fn foo(obj: &Trait) semantically behaves like fn foo<T: Trait>(obj: &T) (incidentally, this is also similar to what happens with implicit lifetime parameters in current Rust), fn foo(obj: Trait) should behave like fn foo<T: Trait>(obj: T). Similarly in the return type position, fn bar() -> Trait should behave from the caller's perspective as-if it were a true existential type, and might be a different type each time the function is called.
(c) Restrictions: The additional restriction relative to boxed trait objects is that any expression which is used as an unboxed trait object must evaluate to the same actual type in all branches. So in argument position, from the caller's side, given fn print_unboxed(arg: Show), print_unboxed(if x { 9i } else { "nine" }) is illegal, even though both types impl Show. Likewise, in return type position, from the callee's perspective, fn some_show(x: bool) -> Show { if x { 9i } else { "nine" } } is illegal, even though the equivalent with a boxed trait object would be legal.
I would allow this only in function signatures, at least in the first round. We can always think about expanding it later on.
Benefits. These decisions are not independent. We are "allowed" to use the same syntax for boxed and unboxed trait objects because we have made the analogy between them precise. This means fewer distinct concepts need to be learned: if you know what "unboxed" and "trait object" mean, you also know what "unboxed trait object" means. We avoid running afoul of "different things should look different" because from a semantic perspective, they are the same thing. This is also helpful for refactoring: for the most part, you can switch between boxed and unboxed trait objects just as you would between boxed and unboxed versions of any concrete type, because the same relationships hold. (I would have said "add or remove sigils", but we don't have many of those any more.)
After reading the RFC, the fact that Box<Trait> and Box<impl Trait> are both meaningful has caused me to waver. This is also consistent, just in a different way. Instead of the natural extension of the boxed trait object concept to unboxed ones, we have a new thing, with new syntax, which itself has internally consistent behavior when used in various different contexts.
What I've outlined above could perhaps be thought of as the minimalist approach, with fewer concepts and less expressiveness, and this RFC as the maximalist one, with more concepts and greater expressiveness. Under the above you can't easily express Box<impl Trait>, and you "know less" about equality between the underlying types of unboxed trait objects than you theoretically could. That said, with respect to Box<impl Trait>, if you really want it, you could still do the newtype thing manually. And if/when we gain support for explicit existential quantification syntax, you could also use that: exists T: Trait. Box<T> corresponds to Box<Trait> and Box<exists T: Trait. T> corresponds to Box<impl Trait>.
|
My opinion prior to reading this RFC was that it would be best to take the trait object analogy and run with it: make the analogy precise both (a) in syntax and (b) in semantics, with the exception of (c) some unavoidable semantic restrictions on the producer of an unboxed trait object relative to the producer of a boxed trait object. (a) Syntax: Given that a boxed type is e.g. (b) Semantics: (c) Restrictions: The additional restriction relative to boxed trait objects is that any expression which is used as an unboxed trait object must evaluate to the same actual type in all branches. So in argument position, from the caller's side, given I would allow this only in function signatures, at least in the first round. We can always think about expanding it later on. Benefits. These decisions are not independent. We are "allowed" to use the same syntax for boxed and unboxed trait objects because we have made the analogy between them precise. This means fewer distinct concepts need to be learned: if you know what "unboxed" and "trait object" mean, you also know what "unboxed trait object" means. We avoid running afoul of "different things should look different" because from a semantic perspective, they are the same thing. This is also helpful for refactoring: for the most part, you can switch between boxed and unboxed trait objects just as you would between boxed and unboxed versions of any concrete type, because the same relationships hold. (I would have said "add or remove sigils", but we don't have many of those any more.) After reading the RFC, the fact that What I've outlined above could perhaps be thought of as the minimalist approach, with fewer concepts and less expressiveness, and this RFC as the maximalist one, with more concepts and greater expressiveness. Under the above you can't easily express |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
aturon
Jun 4, 2014
Member
@eddyb @thestinger The main reason for using some marker like impl is to distinguish Trait (an unsized type under DST) from impl Trait (a sized type in this proposal).
If we restrict the proposal to return types only, I would favor fn A -> _ : Trait as being more consistent with generics.
|
@eddyb @thestinger The main reason for using some marker like If we restrict the proposal to return types only, I would favor |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
aturon
Jun 4, 2014
Member
@pnkfelix In the current proposal, there is no way to provide the concrete types explicitly, which could be problematic.
If we allow impl Trait in argument positions, but not in struct fields, I think it would be sensible to treat them as extra, unnamed type parameters that are positioned after the explicit type parameters in the signature, e.g.,
fn foo<T>(iter: impl Iterator<T>) -> impl Iterator T;
foo::<int, Range<int>>(range(0, 10))But this approach doesn't work so well if impl Trait is allowed in struct fields, because then the parameters depend on the internals of the structs being used in the arguments, which might not even be public! I take this as evidence that allowing impl Trait in struct fields is probably a bad idea.
|
@pnkfelix In the current proposal, there is no way to provide the concrete types explicitly, which could be problematic. If we allow fn foo<T>(iter: impl Iterator<T>) -> impl Iterator T;
foo::<int, Range<int>>(range(0, 10))But this approach doesn't work so well if |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
aturon
Jun 4, 2014
Member
@glaebhoerl That's a very interesting perspective, but I have some questions.
-
The use of
Traitas a type is planned to have a meaning under DST: it will be an unsized type. This is important for the consistency of the type system in other respects, but I think it's incompatible with "unboxed trait objects", which would crucially be sized (since the whole point is that the underlying concrete type is known at compile time.) So I think some kind of marker likeimplis probably needed. Or do you see a way to make the two interpretations compatible? -
If I understand correctly, your proposal is equivalent to:
- the core RFC without the
implmarker (which means thatBox<impl Trait>is impossible, - the function parameters add-on
- none of the other add-ons
Is that right, or are there other differences I'm missing?
- the core RFC without the
Personally, I am favoring something like this more minimal approach (the "Somewhat conservative alternative" I give).
|
@glaebhoerl That's a very interesting perspective, but I have some questions.
Personally, I am favoring something like this more minimal approach (the "Somewhat conservative alternative" I give). |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
blaenk
Aug 23, 2015
Contributor
Yeah tilde is what we had suggested long ago and I think it'd look very nice and it's concise, and I think its association with "approximation" is useful as a mnemonic for its use.
|
Yeah tilde is what we had suggested long ago and I think it'd look very nice and it's concise, and I think its association with "approximation" is useful as a mnemonic for its use. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
-1 for sigils. https://xkcd.com/1306/ |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ticki
Aug 23, 2015
Contributor
cmr:
One of my major ergonomic concerns is that the "fast thing" (static dispatch) is more syntax than the slow thing. We should make the fast thing easy and more natural.
|
cmr:
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Ericson2314
Aug 23, 2015
Contributor
The way I proposed to handle the contextual dependency is to replacing values of that impl Trait type with the inference variable that is used to "collect" the concrete type of the impl Trait.
I'm not exactly sure what you are proposing, but this can only be done within the scope where the the type is transparent: there exists a concrete types that will unify with the inference variable.
Arguably, you could expand on this, and be context-dependent inside a function by deanonymizing impl Traits assigned by the function. Or, in your suggestion, abstract types of the same module.
At least that wouldn't affect unification, but instead work similarly to projections (::Assoc before it's resolved).
But most importantly, you couldn't "witness" the equivalence between the abstract type and its concrete counterpart outside of a function in that module. Would that be good enough?
I'm not sure you mean---this sounds like what you would do outside the scope where the type is transparent / where the type is opaque. Only there does one need to make sure that that equivalence is unobservable. I don't see how to do that without making sure the abstract type and it's concrete definition don't unify.
Multi-function inference can become less painful (read: "less impossible") after the HIR/MIR work, but I wouldn't count on it.
Again, we can always introduce a fresh inference variable for each function in the scope where the type is transparent, and afterwords ensure that that their inferred definitions are the same.
I also fail to see the practical value of such abstract types (emulating parts of a module system we don't have, just for the sake of doing so, doesn't count), whereas returning anonymized types has had applications and a demand for years now.
This is needed for one has multiple functions that return the same abstract type, and at least two construct the type using it's concrete definition.
What can you do with non-inferred abstract types that newtypes don't already handle?
It might be better just to improve on newtypes, if there are rough corners, rather than papering over their issues with a non-orthogonal system borrowed from a world where it is used in a different way (AFAICT, most of its original usecases are handled by traits).
Granted, ML often uses existentials/abstract types where Haskell and Rust use universals:
open Queue;
foo: ...Queue.t...fn foo<T>(....) -> ... where T: Queue;But newtypes are different story. Clearly traits don't help alleviate the need for newtypes---in fact new types are often needed for traits (due to coherence)!
There definitely an overlap betwen "one off" abstract types ("one off" in that since they are part of a module's signature and not a trait, they don't form a "reusable" abstraction) and new types. The most major differences are:
- abstract types are more ergonomic
- newtypes impose less coherence restrictions on downstream crates
- newtypes expose their size (for e.g. transmute) and thus slightly leak
I don't think you can blame my proposal for the overlap, however. Allowing struct MyIter(_); (i.e. the type of the single field is inferred) addresses all the needs this RFC mentions.
On the matter of syntax...
If we're not afraid of recycling sigils...
To be clear, I was only proposing abs / abstract for named abstract types.
I'm not exactly sure what you are proposing, but this can only be done within the scope where the the type is transparent: there exists a concrete types that will unify with the inference variable.
I'm not sure you mean---this sounds like what you would do outside the scope where the type is transparent / where the type is opaque. Only there does one need to make sure that that equivalence is unobservable. I don't see how to do that without making sure the abstract type and it's concrete definition don't unify.
Again, we can always introduce a fresh inference variable for each function in the scope where the type is transparent, and afterwords ensure that that their inferred definitions are the same.
This is needed for one has multiple functions that return the same abstract type, and at least two construct the type using it's concrete definition.
Granted, ML often uses existentials/abstract types where Haskell and Rust use universals: open Queue;
foo: ...Queue.t...fn foo<T>(....) -> ... where T: Queue;But newtypes are different story. Clearly traits don't help alleviate the need for newtypes---in fact new types are often needed for traits (due to coherence)! There definitely an overlap betwen "one off" abstract types ("one off" in that since they are part of a module's signature and not a trait, they don't form a "reusable" abstraction) and new types. The most major differences are:
I don't think you can blame my proposal for the overlap, however. Allowing
To be clear, I was only proposing |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
+1 For |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ticki
Sep 8, 2015
Contributor
I'm afraid that ~ may create confusion as these were sugar for Box before. This might create problems with older material on Rust (docs, tutorials and so on).
|
I'm afraid that |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
mitchmindtree
Sep 8, 2015
Another syntax option is -> @Trait where the @ kind of represents the a in @nonymous/@bstract return type.
However to be honest -> impl Trait has grown on me too - it also reuses existing syntax, is fairly clear and saves fancy sigils for other use cases.
mitchmindtree
commented
Sep 8, 2015
|
Another syntax option is However to be honest |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gnzlbg
Sep 8, 2015
Contributor
Honestly I wish we could just -> Trait and be done with it (no sigil, no keyword, no nothing). The chaining could be just: -> TraitA + TraitB + ....
My second best option would be something like -> R: Trait where R is a generic parameter whose constrain appears on the return type -> R: TraitA + TraitB + ... (I really would prefer the constraints to appear explicitly in the return type instead of with the other generic arguments but I could live with that too).
For reasons I don't understand (yet) we can't have any of these and as @eddyb argues above, impl is too much typing/noise for something that should be common and easy.
Since I read the sigil ~ as approx it makes sense to me in this context. But it is not my first option, and I dislike sigils in general since they are a barrier of entry for newcomers to the language who don't know them (just give a newby a Haskell programs that uses <$> and related operators everywhere). Maybe a better alternative could be to reuse as, like this: -> as TraitA + TraitB + ....
|
Honestly I wish we could just My second best option would be something like For reasons I don't understand (yet) we can't have any of these and as @eddyb argues above, Since I read the sigil |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
eddyb
Sep 8, 2015
Member
Using -> Trait might work, but that prevents nesting the anonymized type in other types (e.g. Box<impl Trait>) and can't be used in associated types at all, as it already has a meaning in those cases.
Generics can't ever be abused for this because the caller can use any type that implements Trait for R, and R has to be provided either directly or via inference.
The closest syntax that could be used is _: Trait, but AFAICT nobody likes that.
|
Using Generics can't ever be abused for this because the caller can use any type that implements The closest syntax that could be used is |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
m4rw3r
Sep 8, 2015
After using impl Trait for a while, it is not so bad at all; here is some example code which uses it: https://github.com/m4rw3r/rust_parser_experiments/blob/ebedd36f2f7e19171c65e38fdee3822d5daa4090/src/main.rs#L235
I agree with you @eddyb regarding generics, not only because it is abuse but also because it creates more noise for the caller. The caller might have to annotate a function, and then the caller either needs to be able to write the type for the generic return (impossible in the case of a closure) or leave a _ in its place which is just unnecessary noise and also something rustc might forbid in some contexts.
_: Trait: to me this looks like you are defining an anonymous generic which implements Trait, since all other uses of _ are anonymous placeholders of some kind.
m4rw3r
commented
Sep 8, 2015
|
After using I agree with you @eddyb regarding generics, not only because it is abuse but also because it creates more noise for the caller. The caller might have to annotate a function, and then the caller either needs to be able to write the type for the generic return (impossible in the case of a closure) or leave a
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
bluss
Sep 8, 2015
An interesting point that I think came up in discussion was that traits lose the main utility of associated types when you use abstract return types, since there is no concrete impl to attach the associated types to. Some kind of aliasing functionality may help with that.
bluss
commented
Sep 8, 2015
|
An interesting point that I think came up in discussion was that traits lose the main utility of associated types when you use abstract return types, since there is no concrete impl to attach the associated types to. Some kind of aliasing functionality may help with that. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
nagisa
Sep 8, 2015
Contributor
fn abcd() ⇝ Trait
Here you go: a sigil, not intrusive, hard to spot and most importantly… extremely hard to type.
What I’m trying to say: this RFC was closed more than a year ago and nobody’s getting this feature in without a new RFC. As (my) previous experience shows, people tend to to go all over the same debate on a follow-up/replacement/new RFCs (see the placement-in + <-), therefore discussing syntax for a feature that’s not landing anytime soon looks like mostly a waste of time (people ignore discussions on previous RFCs most of the time).
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
mitchmindtree
Sep 8, 2015
@nagisa I think most people are aware a new RFC is needed but are just getting some bikeshedding done before the time comes.
mitchmindtree
commented
Sep 8, 2015
|
@nagisa I think most people are aware a new RFC is needed but are just getting some bikeshedding done before the time comes. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ticki
Sep 8, 2015
Contributor
There is a point in making it short, simple, and easy, though, to encourage the programmer to use this over Box (when possible). Naming it AnAbstractUnboxedReturnType does not encourage this. A sigil, on the other side, can also create confusions.
|
There is a point in making it short, simple, and easy, though, to encourage the programmer to use this over |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
aturon
Sep 8, 2015
Member
I've waited more than a month for @aturon and then we had a meeting last week and a new RFC might be coming this month, if that gives you any hope.
Sorry! :(
I have a couple other RFCs in my queue, but hope to push them out this week, and then will focus on reviving this one. Thanks again, @eddyb, for your work on this topic.
Sorry! :( I have a couple other RFCs in my queue, but hope to push them out this week, and then will focus on reviving this one. Thanks again, @eddyb, for your work on this topic. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
mitchmindtree
commented
Sep 8, 2015
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
critiqjo
Sep 20, 2015
The syntax problem is one that will create bikeshed before and after a new RFC, and I'd be glad if we had something optimal before then.
So here's my two cents: I liked the impl Trait syntax, but I feel uneasy that it doesn't play well with the where clause, and things may get really long. So I propose an alternative, inspired from the pattern matching syntax:
fn factory(num: i32) -> T @ _
where T : Fn(i32) -> i32
{
move |x| x + num
}I also liked what @Stebalien proposed here, and here's an alternative:
type X = T @ Arc<_> where T : Send;during compilation X should be resolvable to a single concrete type.
The downside is that you have to use where clause now!
Update: another downside is that it is not obvious from the syntax that T is not a trait... So I guess using impl is the better choice...
critiqjo
commented
Sep 20, 2015
So here's my two cents: I liked the fn factory(num: i32) -> T @ _
where T : Fn(i32) -> i32
{
move |x| x + num
}I also liked what @Stebalien proposed here, and here's an alternative: type X = T @ Arc<_> where T : Send;during compilation The downside is that you have to use Update: another downside is that it is not obvious from the syntax that |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
eddyb
Sep 20, 2015
Member
@critiqjo But that has the wrong semantics: you're requiring that the type implement certain traits but not exposing it.
This is similar to the confusion with generics, and your syntax examples are less ergonomic than even the -> _: Trait syntax, which is the opposite direction of where I'd like this to go.
|
@critiqjo But that has the wrong semantics: you're requiring that the type implement certain traits but not exposing it. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
critiqjo
Sep 20, 2015
That's true... where...
critiqjo
commented
Sep 20, 2015
|
That's true... |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
eddyb
Sep 20, 2015
Member
@critiqjo I honestly don't see the point, what would where bring?
If it was actual existential syntax, it might be interesting, but it's pretty hard to use existentials for fn declarations because the whole fn is existential, not the return type (which would be a plain -> Trait).
|
@critiqjo I honestly don't see the point, what would |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
critiqjo
Sep 20, 2015
Wow! I see!! (I thought where was just to improve readability... Sorry for the noise...)
critiqjo
commented
Sep 20, 2015
|
Wow! I see!! (I thought |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
aturon
Sep 29, 2015
Member
Thanks @glaebhoerl @Ericson2314 @eddyb and others for the insightful discussion since this RFC was closed. I've been thinking about this a fair amount, and after digesting your various comments, wrote up a blog post outlining a couple possible directions.
|
Thanks @glaebhoerl @Ericson2314 @eddyb and others for the insightful discussion since this RFC was closed. I've been thinking about this a fair amount, and after digesting your various comments, wrote up a blog post outlining a couple possible directions. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Stebalien
Sep 29, 2015
Contributor
Nice! Syntax nit. I'd prefer the following over the arrow syntax:
trait IterAdapter: Iterator
where Self: Clone if Self::Inner: Clone,
Self: DoubleEndedIterator if Self::Inner: DoubleEndedIterator
{
type Inner: Iterator;
}To keep APIs sane, I wouldn't allow the inline version.
Also, this alone probably deserves its own RFC (it seems like it would be useful by itself).
|
Nice! Syntax nit. I'd prefer the following over the arrow syntax: trait IterAdapter: Iterator
where Self: Clone if Self::Inner: Clone,
Self: DoubleEndedIterator if Self::Inner: DoubleEndedIterator
{
type Inner: Iterator;
}To keep APIs sane, I wouldn't allow the inline version. Also, this alone probably deserves its own RFC (it seems like it would be useful by itself). |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
mitchmindtree
Sep 29, 2015
Just thought I'd mention there's further discussion of @aturon 's latest blogpost on reddit also.
mitchmindtree
commented
Sep 29, 2015
|
Just thought I'd mention there's further discussion of @aturon 's latest blogpost on reddit also. |
mitchmindtree
referenced this pull request
Sep 29, 2015
Closed
Change `Widget::update` to return a function for mutating the current State rather than produce a whole new State? #554
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
glaebhoerl
Oct 3, 2015
Contributor
@aturon (Going to respond here, because this is where most of the technical discussion has been, and the reddit discussion has fallen off the front pages by now.)
Here are some things which occurred to me while re-reading your post:
If I have my druthers, this feature would also be usable in argument position:
fn map<U>(self, f: ~FnOnce(T) -> U) -> Option<U>
However we end up solving the "abstract return type" use case, I agree it would be nice if it could extend to abstract arguments as well: it bothers me that we currently have to perform the same kind of fn hof<F: FnOnce(...)>(f: F) dance as C++. We should be able to do better.
That said, given the "leaky" semantics of the proposed ~Trait syntax, it seems to me that its analogous behavior in argument position would be much closer to C++ template expansion than to our existing generics:
fn my_print(thing: ~Display) {
println!("{}", thing + 1)
}
my_print(21); // OK, prints 22
my_print("hi"); // compile error
Here, the fact that my_print can depend on the actual type that the caller chose to call it with, rather than just the specified Display interface, is simply dual to how given fn my_printable() -> ~Display, the caller could depend on the type that my_printable chose -- in either case meaning that a change in the implementation can break clients even while the signatures stay the same.
(Personally, this bothers me quite a bit: this is a question of priorities, but explicit interfaces and non-leaky abstractions would be much closer to hard requirements on my list, along with a clean, orthogonal design.)
- It behaves exactly like associated types today.
[...]
- This kind of “leakage” is already prevalent – and important! – in Rust today. For example, when you define an abstract type, you give a trait bound which must be fulfilled. But when a client has narrowed to a particular impl, everything about the associated type is revealed:
Could you spell this analogy out in greater detail? I don't quite have the intuition behind it. (One difference I notice is that with associated types, you do write out the actual type in at least one place, unlike with ~Trait - but it's not obvious to me what it corresponds to in the broader analogy.)
The type leakage is, in general, very unlikely to be relied upon. For example, to observe the particulars of an iterator adapter type, you’d have to do something like assign it to a suitably-typed mutable variable:
let iter: Chain<Map<'a, (int, u8), u16, Enumerate<Filter<'a, u8, vec::MoveItems<u8>>>>, SkipWhile<'a, u16, Map<'a, &u16, u16, slice::Items<u16>>>>; iter = some_function();
I don't understand this example... why couldn't you rely on type inference? Why is mutability relevant? Either way, I don't think I agree with the broader point. On the one hand, maybe this is the case to some extent for iterator adapters, simply because these are special-purpose types whose only purpose in life is to adapt iterators, and there's inherently not much else you can do with them. But in general, most types have much broader interfaces. And on the other hand, I thought leakage for things like conditional impls was the whole point!
The basic idea is to introduce a “type abstraction operator”
@that is used to “seal” a concrete type to a particular interface:pub type File = FileDesc@(Read + Write + Seek + Debug);
This is an intriguing approach, but you don't quite spell it out in the post -- what's the motivation for formulating things this way, rather than e.g. abstract type File: Read + Write + Seek + Debug = FileDesc?
- How should these type definitions interact with coherence? Can you implement traits for
File? Inherent methods? What if they conflict with traits/methods onFileDesc?
The answer feels like it should be "no", or at least, the rules should be akin to the ones for normal type aliases. You definitely shouldn't be able to give conflicting impls for File and FileDesc -- the owning module, at least, should see these as the same type. I guess it's an interesting question that if you do impl Foo for FileDesc and impl Bar for File, and if both File and FileDesc are exported, then outside the module you should be able to know that FileDesc: Foo and File: Bar, but not FileDesc: Bar or File: Foo (which you'd know inside the module, given you know File = FileDesc). That seems logical enough at least for this simple example, but it's kind of subtle and weird, so it might be a better idea to just forbid trait impls directly on abstract types. (Inherent impls seem more desirable... of course you'd like to provide an external API of things you can do with the abstract type, that's kind of the point. While from the owning module's perspective, it should still behave the same as if you were impling on a type alias.)
- How do you deal with bounds where the type isn’t in
Selfposition? For example, there is also an impl ofReadandWritefor&Filethat should be exported.
With the abstract type formulation, at least, it seems natural to use a where clause -- pub abstract type File = FileDesc where File: Read+Write+Seek+Debug, &File: Read+Write;, or somesuch.
|
@aturon (Going to respond here, because this is where most of the technical discussion has been, and the reddit discussion has fallen off the front pages by now.) Here are some things which occurred to me while re-reading your post:
However we end up solving the "abstract return type" use case, I agree it would be nice if it could extend to abstract arguments as well: it bothers me that we currently have to perform the same kind of That said, given the "leaky" semantics of the proposed
Here, the fact that (Personally, this bothers me quite a bit: this is a question of priorities, but explicit interfaces and non-leaky abstractions would be much closer to hard requirements on my list, along with a clean, orthogonal design.)
Could you spell this analogy out in greater detail? I don't quite have the intuition behind it. (One difference I notice is that with associated types, you do write out the actual type in at least one place, unlike with
I don't understand this example... why couldn't you rely on type inference? Why is mutability relevant? Either way, I don't think I agree with the broader point. On the one hand, maybe this is the case to some extent for iterator adapters, simply because these are special-purpose types whose only purpose in life is to adapt iterators, and there's inherently not much else you can do with them. But in general, most types have much broader interfaces. And on the other hand, I thought leakage for things like conditional
This is an intriguing approach, but you don't quite spell it out in the post -- what's the motivation for formulating things this way, rather than e.g.
The answer feels like it should be "no", or at least, the rules should be akin to the ones for normal
With the |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
mitchmindtree
Oct 4, 2015
Edit: here's the reddit discussion.
mitchmindtree
commented
Oct 4, 2015
|
Edit: here's the reddit discussion. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
nrc
Jan 12, 2016
Member
Some thoughts on impl trait here: http://ncameron.org/blog/abstract-return-types-aka-%60impl-trait%60/
One thing I don't address there, but think will work is allowing impls to use a concrete type where the trait uses impl Trait. Then allowing callers which know they have exactly that impl to use the concrete return type
|
Some thoughts on impl trait here: http://ncameron.org/blog/abstract-return-types-aka-%60impl-trait%60/ One thing I don't address there, but think will work is allowing impls to use a concrete type where the trait uses |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
comex
Jan 13, 2016
Wait, you want OIBITs to leak from the function body? That seems like an odd abstraction violation. Why would Sync or Send be different from any other trait in that respect? Isn't part of the motivation to allow stabilizing APIs where the concrete type may change in the future?
comex
commented
Jan 13, 2016
|
Wait, you want OIBITs to leak from the function body? That seems like an odd abstraction violation. Why would |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
eddyb
Jan 13, 2016
Member
@comex The reasoning is the same as with private fields: they are not exposed in the public API but they affect OIBITs.
If we don't reflect OIBITs through impl Trait, they would have to be explicitly specified, which apart from being an annotation nightmare, it would also require support for conditional bounds (e.g. (Send if T: Sync) or (Sync if 'a: 'static)).
And it wouldn't compose, at all. If a new OIBIT is added, existing impl Trait uses wouldn't have it.
My only concerns were about it requiring global inference to implement, but I believe we can create "global obligations" that are checked after all the types are known.
|
@comex The reasoning is the same as with private fields: they are not exposed in the public API but they affect OIBITs. If we don't reflect OIBITs through My only concerns were about it requiring global inference to implement, but I believe we can create "global obligations" that are checked after all the types are known. |
mitchmindtree
referenced this pull request
Feb 4, 2016
Merged
Switch to event-based input handling #684
Ericson2314
referenced this pull request
Sep 6, 2016
Open
Allow explicitly expressing the type of a -> impl Trait #1738
gdox
reviewed
Sep 11, 2016
| The basic idea is to allow code like the following: | ||
| ````rust | ||
| pub fn produce_iter_static() -> impl Iterator<int> { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gdox
Sep 11, 2016
Maybe not the best place to write this, but the impl syntax sounds a bit confusing for me given the impl Trait for Struct syntax we currently have. What about (something like) the following?
pub fn produce_iter_static<I>() -> I guarantees I : Iterator<int> {
range(0, 10).rev().map(|x| x * 2).skip(2)
}This way, the usual syntax of static dispatch (the where-clause) is kept.
As a bonus, it allows:
pub fn produce_iter_static<I>() -> I guarantees I : Iterator<int> + Clone {...}
gdox
Sep 11, 2016
Maybe not the best place to write this, but the impl syntax sounds a bit confusing for me given the impl Trait for Struct syntax we currently have. What about (something like) the following?
pub fn produce_iter_static<I>() -> I guarantees I : Iterator<int> {
range(0, 10).rev().map(|x| x * 2).skip(2)
}This way, the usual syntax of static dispatch (the where-clause) is kept.
As a bonus, it allows:
pub fn produce_iter_static<I>() -> I guarantees I : Iterator<int> + Clone {...}
aturon commentedJun 3, 2014
No description provided.