How i can return an object reference in map that impl a trait

i have a struct with a hashmap, and have a function to return element from map that impl trait, but build fail. what should i do with this function.

use std::collections::HashMap;

trait ISay {
   async fn say(&self);
}

trait IPeopleQueue {
    async fn people_say(&self, name: String) -> impl ISay;
}

struct Hello {}
impl ISay for Hello {
    async  fn say(&self){}
}

struct PeopleQueue {
    peoples: HashMap<String, Hello>
}

impl IPeopleQueue for PeopleQueue {
    async fn people_say(&self, name: String) -> impl ISay {
        self.peoples.get(&name).unwrap()
    }
}

fn main() {
    println!("Hello, world!");
}

this is a simplfy code of that

The issue in this code is that you’re returning a reference &Hello but you’ve only implemented ISay for Hello. If you implement ISay for references, the code compiles:

impl<T:ISay+?Sized> ISay for &T {
    async fn say(&self) { T::say(self).await }
}

Alternatively, you can change the definition of IPeopleQueue to explicitly return references:

trait IPeopleQueue {
    async fn people_say(&self, name: String) -> &impl ISay;
//                                              ^
}

impl IPeopleQueue for PeopleQueue {
    async fn people_say(&self, name: String) -> &impl ISay {
//                                              ^
        self.peoples.get(&name).unwrap()
    }
}
1 Like

it works but i change the the name String to &str, it cant build, this my real code is a id reference.

async fn people_say<'a>(&'a self, name: &str) -> &'a impl ISay;

compiler tell me to add me to add lifetime but cannt work too

    async fn people_say<'a>(&'a self, name: &str) -> &'a impl ISay;

error

8 |     async fn people_say<'a>(&'a self, name: &str) -> &'a impl ISay;
  |     ^^^^^^^^^^^^^^^^^^^^--^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |     |                   |
  |     |                   the associated type `impl ISay` must be valid for the lifetime `'a` as defined here...
  |     ...so that the reference type `&'a impl ISay` does not outlive the data it points at

in my understanding, constraint the impl ISay the same lifetime with self. this should be okay.

use associate type can work

#![feature(trait_alias)]


use std::collections::HashMap;
use anyhow::Result;
use std::hash::Hash;

pub trait GID =
    Eq + Clone + Default + Hash + 'static;


trait ISay {
   async fn say(&self);
}

trait IPeopleQueue<ID>  where ID:GID {
    type Output: ISay;
    async fn people_say(&self, name: &ID) -> Result<&Self::Output>;
}


struct Hello {}
impl ISay for Hello {
    async  fn say(&self){}
}

struct PeopleQueue<ID> {
    peoples: HashMap<ID, Hello>
}

impl<ID> IPeopleQueue<ID> for PeopleQueue<ID> where ID:GID {
    type Output = Hello;

    async fn people_say(&self, name: &ID) ->  Result<&Self::Output>{
        Ok(self.peoples.get(name).unwrap())
    }
}

#[tokio::main]
async fn main() {
    let mut pq = PeopleQueue{
        peoples: HashMap::new()
    };
    pq.peoples.insert("123".to_string(), Hello{});

    let data =  pq.people_say(&"123".to_string()).await.unwrap();
    data.say().await;
}

so whats the difference between

trait IPeopleQueue<ID>  where ID:GID {
    type Output: ISay;
    async fn people_say(&self, name: &ID) -> Result<&Self::Output>;  //work
}

and

trait IPeopleQueue2<ID>  where ID:GID {
    async fn people_say(&self, name: &ID) -> Result<&impl ISay>;  //cannot work
}

it seem that they do the same things. i have a idea that, impl ISay just constrain return type impl ISay but the type maybe different, but for associate type constrain return type impl ISay and also constrain the return is the same type, right?

You are not adding the lifetime everywhere it matters. Since your (async) function body uses the &str, now the resulting future needs to store that too. So the &str has to have (at least) the same lifetime.

This compiles:

async fn people_say<'a>(&'a self, name: &'a str) -> &'a impl ISay;

Ps.: don't prefix traits with "I". Rust is not Java or C#. We have a real and sound type system and great auto-generated documentation. Hungarian notation is totally useless and unwarranted.

1 Like

so assotiate type and impl trait work the same behavior, Or, there not be any correlation; it just happens that both can solve this kind of problem.

Impl trait in a trait method's return type is translated internally to an anonymous associated type. There's no fundamental difference between the two, they are the same high-level idea/mechanism.

There may however be minor differences in the exact desugaring of the two syntaxes that affect the behavior observed here. I'm not 100% sure, but it may be that the impl Trait syntax, following the usual desugaring rules, only captures the lifetime of &self by default, whereas the lifetime(s) of the explicit associated type are determined in a different way.

1 Like

thanks

There may be intricacies I'm missing because

async fn people_say(&self, name: &ID) ->  Result<&impl ISay>

is basically a nested opaque

fn people_say(&self, name: &ID) 
    -> impl Future<Output = Result<&impl ISay>>

but in general, opaque return types capture all generic inputs to the function whereas associated types only capture generic inputs to the implementation and (in the case of GATs) to the associated type itself. Normalization can also let more things compile (opaque types don't normalize to their actual type).

In this case the associated type can only capture generics that are part of the implementing type (because the trait and associated type have no generics of their own). The pertinent part as far as fixing the error goes, though, is that the associated type can't capture the lifetime from name: &ID.


That is to say, in this particular case, I think the problem was just that the opaque impl ISay could contain the lifetime from name: &ID, which would not be valid when it's shorter than the lifetime on &self. Therefore, another fix is to just assert that the opaque is valid for the lifetime on &self.

async fn people_say(&self, name: &ID) -> Result<&(impl ISay + '_)>;

Technically at this point I believe impl ISay is still "capturing" the lifetime from &ID, but in a weaker sense that doesn't inhibit the outlives bound (which I don't fully understand myself and may even be subject to change).[1]


  1. I can go find the hackmd that describes it if anyone is really interested. ↩︎

3 Likes