Join GitHub today
GitHub is home to over 31 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.
This comment has been minimized.
|
FWIW, I feel somewhat uneasy about the I am currently leaning toward the more conservative design detailed under "Alternatives". |
huonw
reviewed
Jun 3, 2014
| 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.
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.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
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.
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
reviewed
Jun 3, 2014
|
|
||
| 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.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
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.
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'.
This comment has been minimized.
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.
This comment has been minimized.
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.
This comment has been minimized.
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)>>>,
}
nrc
reviewed
Jun 3, 2014
| fn extend_dynamic(&mut self, iterator: &Iterator<T>) | ||
| ```` | ||
| It may be especially important for passing unboxed closures as arguments. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
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.
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.)
This comment has been minimized.
This comment has been minimized.
aturon
Jun 3, 2014
Author
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.
This comment has been minimized.
aturon
Jun 3, 2014
Author
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.
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.
This comment has been minimized.
This comment has been minimized.
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
|
|
||
| # Summary | ||
|
|
||
| Allow functions to return types to return _unboxed abstract types_, written |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
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.
This comment has been minimized.
|
Added notes to the RFC on an additional choice: allowing |
This comment has been minimized.
This comment has been minimized.
|
@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.
This comment has been minimized.
|
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.
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.
This comment has been minimized.
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>
This comment has been minimized.
This comment has been minimized.
|
@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.
This comment has been minimized.
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.
This comment has been minimized.
|
@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.
This comment has been minimized.
|
I think the meaning of |
This comment has been minimized.
This comment has been minimized.
|
(Also, the notation |
This comment has been minimized.
This comment has been minimized.
Which |
This comment has been minimized.
This comment has been minimized.
|
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.
This comment has been minimized.
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.
This comment has been minimized.
|
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.
This comment has been minimized.
|
This comment has been minimized.
This comment has been minimized.
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.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
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.
This comment has been minimized.
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.
This comment has been minimized.
mitchmindtree
commented
Sep 8, 2015
This comment has been minimized.
This comment has been minimized.
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.
This comment has been minimized.
|
@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.
This comment has been minimized.
critiqjo
commented
Sep 20, 2015
|
That's true... |
This comment has been minimized.
This comment has been minimized.
|
@critiqjo I honestly don't see the point, what would |
This comment has been minimized.
This comment has been minimized.
critiqjo
commented
Sep 20, 2015
|
Wow! I see!! (I thought |
This comment has been minimized.
This comment has been minimized.
|
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.
This comment has been minimized.
|
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.
This comment has been minimized.
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.
This comment has been minimized.
|
@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.
This comment has been minimized.
mitchmindtree
commented
Oct 4, 2015
|
Edit: here's the reddit discussion. |
This comment has been minimized.
This comment has been minimized.
|
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.
This comment has been minimized.
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.
This comment has been minimized.
|
@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
Closed
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.
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 {...}
aturon commentedJun 3, 2014
No description provided.