Skip to content
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

Idea: default types for traits #2211

Open
canndrew opened this Issue Nov 7, 2017 · 8 comments

Comments

Projects
None yet
6 participants
@canndrew
Copy link
Contributor

canndrew commented Nov 7, 2017

Some types are only visible through the traits that they implement. Currently, when Rust cannot infer a type it will either raise an error or, in some unclearly-defined situations, default the type to ! (or ()). Perhaps we could allow defaulting in more situations and allow the defaulting to be trait-directed.

For example, the signature of Iterator::unzip is:

fn unzip<A, B, FromA, FromB>(self) -> (FromA, FromB) where
    FromA: Default + Extend<A>,
    FromB: Default + Extend<B>,
    Self: Iterator<Item = (A, B)>, 

Now suppose I write:

let (_, foo) = some_iterator.unzip();
...
for x in foo {
    ...
}

If I haven't touched foo anywhere else my code, then this won't compile. All rust knows about foo's type is that it must impl Default + Extend<X> + IntoIterator<Item=X>, and there may be lots of types that satisfy this requirement. However Vec<X> is, in some sense, the canonical type that satisfies these requirements - when iterated it gives back the exact same items that where .extend-ed into it, in the exact same order. Perhaps the compiler should be able to infer Vec<X> in this case. Perhaps the standard library should be able to specify Vec<X> as the inferred type in cases like this with a declaration like:

default<A> Default + Extend<A> + IntoIterator<Item=A> = Vec<A>;

Multiple default declarations could override each other by being more specific in much the same way that impl specialization works. When the compiler tries to default a type which satisfies some required bounds B, it looks for the most specific trait A <: B for which there is a default type, and uses that type if it satisfies B.

The current behaviour of defaulting to ! could be replaced by a declaration in libcore of:

default ?Sized = !;

There could also be default of (just for example):

default Default = ();

So, is this a good idea? Or is it terrible?

@rkruppe

This comment has been minimized.

Copy link
Member

rkruppe commented Nov 7, 2017

My only thought on the matter:

However Vec is, in some sense, the canonical type that satisfies these requirements - when iterated it gives back the exact same items that where .extend-ed into it, in the exact same order.

This property is far from unique. Even in std there's VecDeque (which could be disregarded as being more complex and hence less canonical) and LinkedList (which can't be dismissed on the same grounds). I could still see an argument for Vec being the default sequence type (and in practice, I don't think many people would dispute that), but one that is less nice and clean-cut than just pointing at this property.

@burdges

This comment has been minimized.

Copy link

burdges commented Nov 7, 2017

If you're in no_std land, then ArrayVec<?> sounds more natural. Right now, the ? remains ambiguous, but maybe not if we get VLAs and know the size. And ArrayVec might eventually become superfluous, giving just a VLA.

@ExpHP

This comment has been minimized.

Copy link

ExpHP commented Nov 7, 2017

I wish this were a property of the functions rather than the known trait bounds. I feel like a default of Vec<_> should somehow be tied to Iterator::{collect, unzip, partition}.

Type parameter defaults for functions were accidentally supported in the grammar for some time and I'm sure I've written them a number of times expecting them to do something. I gather from that issue that it is not necessarily clear how these should behave, but without being able to find much discussion on it I'm not sure I see how they are any muddier than defaults for parameters in types and traits.


I guess the difference for structs and traits is that those defaults are applied each and every time we write out the type, regardless of context. Whereas in the case of functions, we're moreso hoping for the default to merely act as a "suggestion" to type inference.

@Centril Centril added the T-lang label Dec 6, 2017

@leodasvacas

This comment has been minimized.

Copy link

leodasvacas commented Feb 28, 2018

@canndrew

This comment has been minimized.

Copy link
Contributor Author

canndrew commented Mar 4, 2018

One place this could be useful is in automatic-enuming of return types in futures. For example: say I have

.and_then(|x| {
    match x {
        true => some_future,
        false => some_other_future,
    }
})

This won't compile because some_future and some_other_future have different types. Instead we have to box them:

.and_then(|x| {
    match x {
        true => Box::new(some_future) as Box<Future<...>>,
        false => Box::new(some_other_future) as Box<Future<...>>,
    }
})

However this does an unnecessary allocation. What we really want is to create an anonymous 2-variant enum that implements Future. Using this RFC, and the anonymous enums RFC, and the generic-sized tuples RFC (extended to anonymous enums), we could make the default type for Future<Item=T, Error=E> + From<A0> + From<A1> + ... + From<An> where A*: Future<Item=T, Error=E> be an anonymous enum over A0 .. An which implements Future<Item = T, Error=E>.

@burdges

This comment has been minimized.

Copy link

burdges commented Mar 5, 2018

If you're already doing a Box<Future> then an Either as impl Future works just as well, right? I suppose Either<impl Future,impl Future> even works.

@canndrew

This comment has been minimized.

Copy link
Contributor Author

canndrew commented Mar 16, 2018

@burdges I guess so, yeah Would still be nice to have generic-arity anonymous enums for this though.

@burdges

This comment has been minimized.

Copy link

burdges commented Mar 16, 2018

We could maybe unify the various Either types so the same Eithertype worked for numerous traits, including Iterator, Future, Fn*, Clone, *Hash*, some of Deref*, Borrow*, As*, and some important traits from elsewhere like Rng. If this exists, then one might build an anonify!(..) proc macro to automatically build Either trees.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.