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

Tracking Issue for RFC 213: Default Type Parameter Fallback #27336

Open
jroesch opened this Issue Jul 27, 2015 · 54 comments

Comments

Projects
None yet
@jroesch
Copy link
Member

jroesch commented Jul 27, 2015

This is a tracking issue for RFC 213.

The initial implementation of this feature has landed.

cc @nikomatsakis

@bluss

This comment has been minimized.

Copy link
Contributor

bluss commented Nov 20, 2015

What is the status of this?

@pnkfelix

This comment has been minimized.

Copy link
Member

pnkfelix commented Dec 1, 2015

@bluss it is still feature-gated, AFAICT. See e.g. the discussion on PR #26870 , or just look at this playpen

I am not sure what the planned schedule is for unfeature-gating it.

nominating for discussion.

@mahkoh

This comment has been minimized.

Copy link
Contributor

mahkoh commented Dec 11, 2015

This doesn't seem to be working properly.

#![crate_type = "lib"]
#![feature(default_type_parameter_fallback)]

trait A<T = Self> {
    fn a(t: &T) -> Self;
}

trait B<T = Self> {
    fn b(&self) -> T;
}

impl<U, T = U> B<T> for U
    where T: A<U>
{
    fn b(&self) -> T {
        T::a(self)
    }
}

struct X(u8);

impl A for X {
    fn a(x: &X) -> X {
        X(x.0)
    }
}

fn f(x: &X) {
    x.b(); // ok
}

fn g(x: &X) {
    let x = x.b();
    x.0; // error: the type of this value must be known in this context
}
@jroesch

This comment has been minimized.

Copy link
Member Author

jroesch commented Dec 15, 2015

@mahkoh there is a necessary patch that hasn't gotten rebased since I stopped my summer internship. I've been unfortunately busy with real life stuff, looks like @nikomatsakis has plans for landing a slightly different version according to a recent post of his on the corresponding documentation issue for this feature.

@bluss

This comment has been minimized.

Copy link
Contributor

bluss commented Feb 22, 2016

@nikomatsakis I know the lang team didn't see any future in this feature, will you put that on record in the issue 😄?

One example where this feature seems to be the only way out is the following concrete example of API evolution in libstd.

Option<T> implements PartialEq today, but we would like to extend it to PartialEq<Option<U>> where T: PartialEq<U>. It appears this feature can solve the type inference regressions that would otherwise occur (and might block us from doing this oft-requested improvement of Option).

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Feb 23, 2016

@bluss I HAVE been dubious of this feature, but I'm been slowly reconsidering. @aturon is supposed to be doing some exploration of this whole space and writing up some detailed thoughts. I actually started rebasing @jroesch's dead branch to implement the desired semantics and making some progress there too, but I've been distracted.

One advantage of finishing up the impl is that it would let us experiment with extensions like the one you describe to see how backwards compatible they truly are -- one problem with fallback is that it is not ACTUALLY backwards compatible, because of the possibility of competing incompatible fallbacks.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Feb 23, 2016

That said I still have my doubts :)

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Feb 23, 2016

Another example where this could be useful -- basically the same example as petgraph -- is adding allocators to collections in some smooth way.

@durka

This comment has been minimized.

Copy link
Contributor

durka commented Feb 24, 2016

What are the drawbacks to turning this on? It seems to mainly make things compile that otherwise cannot infer enough type information.

@abonander

This comment has been minimized.

Copy link
Contributor

abonander commented Feb 25, 2016

I have a pretty good use for this too. It's basically what @bluss mentioned, adding new types to an impl while avoiding breaking inference on existing usage.

@withoutboats

This comment has been minimized.

Copy link
Contributor

withoutboats commented Apr 5, 2016

Is the only issue with this the interaction with numeric fallback? I like default type parameters a lot. I often use them when I parameterize a type which has only one production instantiation, for mocking and to enforce bondaries. Its inconsistent and for me unpleasant that defaults don't work for the type parameters of functions.

bors-servo added a commit to servo/webrender that referenced this issue Apr 10, 2017

Auto merge of #1098 - Gankro:with_hasher, r=glennw
Use shorter synonym for Hashmap::with_hasher(Default::default())

`HashMap::new` *should* have this behaviour, but has been eternally blocked by this lang feature: rust-lang/rust#27336. Specifically `HashMap::new` would fail to infer a hasher if it was ambiguous (most test/example code).

However we sneakily made the Default implementation generic over Hasher, so this works.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/webrender/1098)
<!-- Reviewable:end -->
@Gankro

This comment has been minimized.

Copy link
Contributor

Gankro commented May 11, 2017

It's really unfortunate that this is blocking #32838. Is there anything I can do to help push this forward, outside of implementation work?

@Ericson2314

This comment has been minimized.

Copy link
Contributor

Ericson2314 commented May 11, 2017

I don't think it's blocking that. We can always just newtype all of collections in std, and move hashmap into collections while we are at it.

It's an ugly solution, but only a temporary once. The allocator traits are far enough behind schedule that we should seriously consider it.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented May 11, 2017

@Gankro I think we're still sort of in need of a good survey of the space of ideas and a nice summary. That would be helpful.

@mitsuhiko

This comment has been minimized.

Copy link
Contributor

mitsuhiko commented Sep 23, 2017

I have a few more cases where this is really useful. In particular I have a situation where I have a generic function that requires type inference for the return value. However if there is no user of the return value it requires annotations even though I would have perfectly sensible defaults that then get optimized away.

Think of it like this:

// currently required
let _: () = my_generic_function()?;

// with a unit default
my_generic_function()?;
@leodasvacas

This comment has been minimized.

Copy link
Contributor

leodasvacas commented Nov 13, 2017

I'm no expert but I'll try to summarize and contribute.

We want to use type defaults to inform inference. However we may get multiple defaults that could apply to an inference variable, resulting in a conflict. Also new defaults may be added in the future, causing conflicts where there was none.

@eddyb proposes the conservative approach of erroing on conflict and erroing when trying to apply a default to a type with no default, for future-proofing. The consequence is that all defaults must be present and must agree to be applied.

Let's take the reasonable and useful example by @joshtriplett. It would not work because it's trying to unify T in struct Option<T> with P in fn func<P: AsRef<Path> = String>(p: Option<P>). Even if Option<T> were to gain a default for T, intuitively func should not care because it's default is more local and should take precedence.

So I propose that local defaults trump type defaults. So fn and impl defaults would take precedence over the default on the type, meaning that any future conflict between T and P above would always be solved by using the default on P, making it future-proof to apply P's default which is String.

Note that this is limited to literals, if the value came from another fn as in:

fn noner<T>() -> Option<T> { None }

fn main() {
    // func is same as in original example.
    func(noner());
}

Then we are back to an error.

This a small yet useful extension, together with the slice::sort example this makes a case for allowing type defaults in impls and fns. Hopefully we are coming to a minimal but useful version of this feature that we could stabilize.

Edit: If we give fns and impls preference on the defaults, then we should not inherit the default from the type as that would defeat the point of adding the default being non-breaking.

Edit 2:

no universal priority ordering can really exist

Given a set of type variables in context that could be unified by a default, can't we order those variables by the order they are introduced? And then consider them in order, trying their defaults and failing at the first variable with no default.

@DoumanAsh

This comment has been minimized.

Copy link

DoumanAsh commented Jul 17, 2018

Found this issue when I was looking into RFC.

I noticed recently a problem for myself when I have default type paramer, but my impl is generic.
So currently I have two options:

  • impl for struct with default type parameter
  • Ask users to use a bit ugly <Struct> syntax to guarantee default type paramer.

I wonder if it would be sensible to allow impl blocks to have default type parameters while retaining ability to specify trait boundry.

e.g.

pub trait MyTrait {}

struct MyStruct;
impl MyTrait for MyStruct;

struct API<M: MyStruct> {
....
}

impl<M: MyTrait=MyStruct> for API<M> {
    pub fn new() -> Self<M> {
    }
}

I may understand reasoning for impl blocks to overshadow default parameter when invoking API::new() but it is a bit sad when you cannot clearly set default type parameter for impl block alongside your struct.

@lachlansneff

This comment has been minimized.

Copy link

lachlansneff commented Feb 17, 2019

Could this be fast tracked? Having better type inference would allow better user-experience for collections with custom allocators. #42774 (comment)

@Gankro

This comment has been minimized.

Copy link
Contributor

Gankro commented Feb 26, 2019

The proposal is in currently in a zombie state due to disagreements among the lang-team over how to resolve issues that were discovered during implementation. No progress has been made as it is otherwise regarded as low priority.

@jethrogb

This comment has been minimized.

Copy link
Contributor

jethrogb commented Feb 27, 2019

Maybe that priority should be re-evaluated? I think this is blocking various library improvements such as std-compatible HashMap in alloc, custom allocators, etc.

@Gankro

This comment has been minimized.

Copy link
Contributor

Gankro commented Feb 27, 2019

I'm not aware of any reason custom allocators would be blocked on default-affects-inference. They can be added backwards-compatibly in the same way hashers currently work -- ::new and friends would hardcode the default, and Default::default/with_allocator would be properly generic.

@npmccallum

This comment has been minimized.

Copy link
Contributor

npmccallum commented Mar 27, 2019

Is this the same issue I'm running into here?

pub struct Foo<T: Default = u64>(T);

impl<T: Default> Foo<T> {
    pub fn make() -> Foo<T> {
        Foo(T::default())
    }
}

fn main() {
    // error[E0283]: type annotations required: cannot resolve `_: std::default::Default`
    let foo = Foo::make();
}
@Gankro

This comment has been minimized.

Copy link
Contributor

Gankro commented Mar 28, 2019

Yes.

@Ericson2314

This comment has been minimized.

Copy link
Contributor

Ericson2314 commented Mar 29, 2019

@Gankro

I'm not aware of any reason custom allocators would be blocked on default-affects-inference. They can be added backwards-compatibly in the same way hashers currently work -- ::new and friends would hardcode the default, and Default::default/with_allocator would be properly generic.

The default hasher RandomState for HashMap cannot go in alloc because it accesses os-specific sources of randomness. (https://doc.rust-lang.org/src/std/collections/hash/map.rs.html#3165-3186) We therefore need to define the default "after the fact". rust-lang/rfcs#2492 is the only way I know to get around this.

I'd also like to define collections prior to the notion of global allocation being introduced, which would piggyback on the solution for RandomState. This, of course, is not a blocker.

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.