Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
772 views
in Technique[技术] by (71.8m points)

rust - How do I specify lifetime parameters in an associated type?

I have this trait and simple structure:

use std::path::{Path, PathBuf};

trait Foo {
    type Item: AsRef<Path>;
    type Iter: Iterator<Item = Self::Item>;

    fn get(&self) -> Self::Iter;
}

struct Bar {
    v: Vec<PathBuf>,
}

I would like to implement the Foo trait for Bar:

impl Foo for Bar {
    type Item = PathBuf;
    type Iter = std::slice::Iter<PathBuf>;

    fn get(&self) -> Self::Iter {
        self.v.iter()
    }
}

However I'm getting this error:

error[E0106]: missing lifetime specifier
  --> src/main.rs:16:17
   |
16 |     type Iter = std::slice::Iter<PathBuf>;
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^ expected lifetime parameter

I found no way to specify lifetimes inside that associated type. In particular I want to express that the iterator cannot outlive the self lifetime.

How do I have to modify the Foo trait, or the Bar trait implementation, to make this work?

Rust playground

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

There are a two solutions to your problem. Let's start with the simplest one:

Add a lifetime to your trait

trait Foo<'a> {
    type Item: AsRef<Path>;
    type Iter: Iterator<Item = Self::Item>;
    
    fn get(&'a self) -> Self::Iter;
}

This requires you to annotate the lifetime everywhere you use the trait. When you implement the trait, you need to do a generic implementation:

impl<'a> Foo<'a> for Bar {
    type Item = &'a PathBuf;
    type Iter = std::slice::Iter<'a, PathBuf>;
    
    fn get(&'a self) -> Self::Iter {
        self.v.iter()
    }
}

When you require the trait for a generic argument, you also need to make sure that any references to your trait object have the same lifetime:

fn fooget<'a, T: Foo<'a>>(foo: &'a T) {}

Implement the trait for a reference to your type

Instead of implementing the trait for your type, implement it for a reference to your type. The trait never needs to know anything about lifetimes this way.

The trait function then must take its argument by value. In your case you will implement the trait for a reference:

trait Foo {
    type Item: AsRef<Path>;
    type Iter: Iterator<Item = Self::Item>;
    
    fn get(self) -> Self::Iter;
}

impl<'a> Foo for &'a Bar {
    type Item = &'a PathBuf;
    type Iter = std::slice::Iter<'a, PathBuf>;
    
    fn get(self) -> Self::Iter {
        self.v.iter()
    }
}

Your fooget function now simply becomes

fn fooget<T: Foo>(foo: T) {}

The problem with this is that the fooget function doesn't know T is in reality a &Bar. When you call the get function, you are actually moving out of the foo variable. You don't move out of the object, you just move the reference. If your fooget function tries to call get twice, the function won't compile.

If you want your fooget function to only accept arguments where the Foo trait is implemented for references, you need to explicitly state this bound:

fn fooget_twice<'a, T>(foo: &'a T)
where
    &'a T: Foo,
{}

The where clause makes sure that you only call this function for references where Foo was implemented for the reference instead of the type. It may also be implemented for both.

Technically, the compiler could automatically infer the lifetime in fooget_twice so you could write it as

fn fooget_twice<T>(foo: &T)
where
    &T: Foo,
{}

but it's not smart enough yet.


For more complicated cases, you can use a Rust feature which is not yet implemented: Generic Associated Types (GATs). Work for that is being tracked in issue 44265.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...