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

Trait implementation accepts wrong lifetime in method signature with associated type #22077

Closed
tomjakubowski opened this issue Feb 8, 2015 · 8 comments · Fixed by #24461
Closed
Assignees
Labels
A-associated-items Area: Associated items (types, constants & functions) A-lifetimes Area: Lifetimes / regions
Milestone

Comments

@tomjakubowski
Copy link
Contributor

(Sorry for the bad title)

trait Fun {
    type Output;
    fn call<'x>(&'x self) -> Self::Output;
}

struct Holder { x: String }

impl<'a> Fun for Holder {
    type Output = &'a str;
    fn call<'b>(&'b self) -> &'b str {
        &self.x[]
    }
}

This compiles even though the trait's definition should require call's signature in the impl to be fn call<'b>(&'b self) -> &'a str.

@huonw
Copy link
Member

huonw commented Feb 8, 2015

This seems to be a problem with associated types, converting Fun to Fun<Output> correctly fails to compile:

trait Fun<Output> {
    fn call<'x>(&'x self) -> Output;
}

struct Holder { x: String }

impl<'a> Fun<&'a str> for Holder {
    fn call<'b>(&'b self) -> &'b str {
        &self.x[]
    }
}
<anon>:8:5: 10:6 error: method `call` has an incompatible type for trait: expected bound lifetime parameter 'x, found concrete lifetime [E0053]
<anon>:8     fn call<'b>(&'b self) -> &'b str {
<anon>:9         &self.x[]
<anon>:10     }

cc @nikomatsakis

@huonw huonw added A-associated-items Area: Associated items (types, constants & functions) I-nominated A-lifetimes Area: Lifetimes / regions labels Feb 8, 2015
@tomjakubowski tomjakubowski changed the title Trait implementation accepts incorrect signature Trait implementation accepts wrong lifetime in impl's method signature Feb 8, 2015
@tomjakubowski tomjakubowski changed the title Trait implementation accepts wrong lifetime in impl's method signature Trait implementation accepts wrong lifetime in method signature Feb 8, 2015
@huonw huonw changed the title Trait implementation accepts wrong lifetime in method signature Trait implementation accepts wrong lifetime in method signature with associated type Feb 8, 2015
@edwardw
Copy link
Contributor

edwardw commented Feb 8, 2015

cc me. It feels very closely related to #21750, although on the surface they are quite the opposite. Also, @nikomatsakis may have something in the making for #21974.

@pnkfelix
Copy link
Member

P-backcompat-lang, 1.0 beta.

@pnkfelix pnkfelix added this to the 1.0 beta milestone Feb 12, 2015
@nikomatsakis nikomatsakis self-assigned this Feb 12, 2015
@huonw
Copy link
Member

huonw commented Feb 13, 2015

Possibly related to #21259.

@edwardw
Copy link
Contributor

edwardw commented Feb 14, 2015

Have an fix in the same vein of #22338. But, it also reports an error

error: method index_mut has an incompatible type for trait: expected type parameter, found a different type parameter [E0053]

for libstd/collections/hash/map.rs:

impl<K, Q: ?Sized, V, S, H> Index<Q> for HashMap<K, V, S>
    where K: Eq + Hash<H>,
          Q: Eq + Hash<H> + BorrowFrom<K>,
          S: HashState<Hasher=H>,
          H: hash::Hasher<Output=u64>
{
    type Output = V;

    #[inline]
    fn index<'a>(&'a self, index: &Q) -> &'a V {
        self.get(index).expect("no entry found for key")
    }
}

impl<K, V, S, H, Q: ?Sized> IndexMut<Q> for HashMap<K, V, S>
    where K: Eq + Hash<H>,
          Q: Eq + Hash<H> + BorrowFrom<K>,
          S: HashState<Hasher=H>,
          H: hash::Hasher<Output=u64>
{
    #[inline]
    fn index_mut<'a>(&'a mut self, index: &Q) -> &'a mut V {
        self.get_mut(index).expect("no entry found for key")
    }
}

Notice, the order of the type parameters is different. So is the error legit or does the fix simply have a regression?

edwardw added a commit to edwardw/rust that referenced this issue Feb 22, 2015
Otherwise, the substitution is carried out from outside perspective,
which would be against the very purpose of `ParameterEnvironment`.

Closes rust-lang#21750
Closes rust-lang#22077
@nikomatsakis
Copy link
Contributor

Ah, I see. The problem is that the 'a variable on the impl is unconstrained. That is, this impl promises that it can make an Output for any 'a, but 'a does no appear in the input type nor in the trait reference. I decided to allow such "unused" lifetimes even though, probably, they ought to be prohibited by RFC 447 because it wasn't clear to me what harm they could do. Given only methods, I don't think they really can do harm, though they are kind of pointless. In particular, if the lifetime does not appear in the trait reference, it cannot appear in the signature of any of the methods that the trait defines, so when someone calls fns defined in the trait, they can use any lifetime they want for that impl parameter without affecting the signature they see.

But this logic does not hold with associated types. They allow us to create links between a projected type and the return type of a method. In that case, it is harmful if there are lifetimes that are not constrained by the trait reference or the input types, since every instantiation of Self::Output is then free to choose a distinct 'a with no constraints. Even this is not bad, except that when we call a trait fn, we assume that every use of <X as Trait>::Output will be the same type if the inputs to the trait (X, in this case) are the same, but that is not the case here.

We should prohibit these unconstrainted lifetime parameters, either completely, or at minimum from appearing in the value of an associated type.

@emberian
Copy link
Member

Example illustrating soundness hole:

struct Foo<'a> {
    inner: &'a mut [u8]
}

impl<'a, 'b> Iterator for Foo<'a> {
    type Item = &'b mut u8;

    fn next(&mut self) -> Option<&mut u8> {
        Some(&mut self.inner[0])
    }
}

fn main() {
    let mut fun = [1,2,3,4,5];
    let mut foo = Foo {
        inner: &mut fun
    };
    {
        let a = foo.next().unwrap();
        let b = foo.next().unwrap();
        *a = 1;
        *b = 2;
        println!("{}", a);
    }
}

@pnkfelix
Copy link
Member

This is "just a bug", but one we would really like to fix.

Nonetheless, not a beta-blocker. But we want to fix for 1.0; so reclassifying as such.

@pnkfelix pnkfelix added this to the 1.0 milestone Mar 26, 2015
@pnkfelix pnkfelix removed this from the 1.0 beta milestone Mar 26, 2015
bors added a commit that referenced this issue Apr 17, 2015
…turon

This makes it illegal to have unconstrained lifetimes that appear in an associated type definition. Arguably, we should prohibit all unconstrained lifetimes -- but it would break various macros. It'd be good to evaluate how large a break change it would be. But this seems like the minimal change we need to do to establish soundness, so we should land it regardless. Another variant would be to prohibit all lifetimes that appear in any impl item, not just associated types. I don't think that's necessary for soundness -- associated types are different because they can be projected -- but it would feel a bit more consistent and "obviously" safe. I'll experiment with that in the meantime.

r? @aturon 

Fixes #22077.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-associated-items Area: Associated items (types, constants & functions) A-lifetimes Area: Lifetimes / regions
Projects
None yet
6 participants