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 · 47 comments

Comments

Projects
None yet
@jroesch
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.

Contributor

bluss commented Nov 20, 2015

What is the status of this?

@pnkfelix

This comment has been minimized.

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.

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.

Member

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.

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.

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.

Contributor

nikomatsakis commented Feb 23, 2016

That said I still have my doubts :)

@nikomatsakis

This comment has been minimized.

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.

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.

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.

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.

@cramertj

This comment has been minimized.

Member

cramertj commented Sep 13, 2016

@jsen- I think you misunderstood my point. I had thought I had discovered a bug-- default type parameters shouldn't be usable on functions (currently). However, I was just mistaking numeric defaults for something more sinister.

@nikomatsakis

This comment has been minimized.

Contributor

nikomatsakis commented Sep 13, 2016

@jsen- it doesn't directly help you, but it occurs to me that if we had default fn parameters, one could (maybe?) write:

pub trait ExitCodeValidator {
    fn validate(&self, exit_code: i32) -> bool;
}

pub struct DefaultExitCodeValidator;

impl ExitCodeValidator for DefaultExitCodeValidator {
    fn validate(&self, exit_code: i32) -> bool {
        return exit_code == 0;
    }
}

pub fn execute_command<T, V>(
    cmd: &mut Command,
    validator: V = DefaultExitCodeValidator)
    -> Result<T>
where V: ExitCodeValidator,
{
    let output = ...;
    let exit_code = ...;

    if validator.validate(exit_code) {
        Ok(output)
    } else {
        Err(InvalidExitCode(exit_code))
    }
}

This seems better than your existing code because it allows an exit code validator to carry some state. But of course it relies on a future that's not implemented, and in particular on the ability to instantiate a default parameter of generic type.

@jsen-

This comment has been minimized.

Contributor

jsen- commented Sep 13, 2016

@nikomatsakis right, I just shortened my current implementation, which relies on default_type_parameter_fallback.
I'd sure love to see default function arguments, but I'm not even aware of any plans to include them in the language (I'll sure look around in the RFC section 😃)
I posted the question because we need to move to stable some day 😉
Edit: looking again at your example, this time with with both eyes open (it's after midnight here already 😆), you didn't mean default function arguments. But my goal is not specify the Validator at all in the usual case. But I'm probably overthinking it, because I agree with explicit is better than explicit

@cramertj yeah, I was confused. Based on your comment I got an idea, that I could emulate it with struct defaulted type params.
Something like this:

// defaulted:
Exec::execute_command(cmd);
// explicit:
Exec::<MyExitCodeValidator>::execute_command(cmd);

...but I'm finding this is not be possible as well. I'm not yet fully shifted away from C++'s meta-programming. Definitely still need to learn a lot about rust.

@eddyb

This comment has been minimized.

Member

eddyb commented Sep 13, 2016

@jsen- Try <Exec>::execute_command(cmd) (that way the path is in a type context, not expression context, and the defaults are forcefully applied).

@jsen-

This comment has been minimized.

Contributor

jsen- commented Sep 13, 2016

@eddyb thanks a lot, that worked perfectly
Here's a link to somewhat contrived, but working example if that helps anyone who finds this

@nikomatsakis

This comment has been minimized.

Contributor

nikomatsakis commented Nov 28, 2016

adding a link to this (old) internals thread for posterity:

https://internals.rust-lang.org/t/interaction-of-user-defined-and-integral-fallbacks-with-inference/2496

it discusses the interaction of RFC 213 with integral fallback, though similar issues can also arise without using integers, if you just have multiple functions with competing fallback.

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.

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.

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.

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.

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.

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.

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.

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