Is there an automatic way of supplying the lifetime parameters?

I have the following method which borrows data with a lifetime 'a and returning a future also supposed to live that long because I am using the function and awaiting on it to finish on the same scope as the actual variables I am referencing. (I am pretty new to the concept of borrowing in this level I hope I could express myself)

pub trait Store<'a> {
    fn new_game(
        &self,
        name: &'a str,
        creator_id: &'a str,
        data_supplier_id: &'a str,
        data_supplier_pass: &'a str,
        video_addr: &'a Option<String>,
        auto: bool,
        cards: &'a Option<Card>,
    ) -> Pin<Box<dyn Future<Output = Result<i32>> + 'a>>;

First of all it feels like I am doing something wrong and second question is that unless I do something wrong is there a nicer way of defining those lifetimes without writing 'a all over the place?

Don't put temporary references in structs.

The need to put a lot of <'a> is a red flag that you're misunderstanding references as storing data by reference, rather than as a scope-limited borrow.

Box and String pass and store data by reference. & is a compile-time lock that pins a view of data to a scope and prevents the actual data (that must be stored elsewhere) from being modified or moved for lifetime of its borrow.

Instead of temporary string view &str that doesn't store the name, use String which stores the name. Same with all other fields. Owned data doesn't have any lifetime, so it doesn't require any annotations to limit its use.

BTW, &Option<Card> is a type that is the worst of both worlds: it's non-trivial to construct, because it requires owned content and moving it into the Option, and it's made scope-bound and read-only by the borrow. If you're storing it, store owned Option<Card>. If you're lending/borrowing it, use Option<&Card> instead (see Option.as_ref()), which can be cheaply constructed from a borrow of &Card.

2 Likes

In my case I dont want to copy data coming from my endpoints all over the place.
And the data is guaranteed to be read only Isnt copying them from one place to another an unnecessary hassle ?

isnt avoiding copy if not needed a good practice ?

1 Like

All else being equal, yes avoiding copies is good (modulo cases where you want to mutate one and not the other, so the copy is semantically required). But all else is not equal here.

In general, the desire to "store" a value inside a struct strongly implies that you need a copy anyway. If you're new to Rust, just make copies for now; reference fields are simply not worth the loss of flexibility. After your program is fully functional you can go back and think about which copies are avoidable and which aren't.

In my case I am not actually storing it in a struct this is a function gets those references and creates a db entry from them so I dont need really the params to live longer than that

In that case, you shouldn't need any of these lifetime annotations.

If just removing them doesn't work, could you provide a more complete code sample to discuss?

I need the type annotations while implementing this trait on a struct and I get error message saying basically dyn future is supposed to be 'static but It an live longer than my parameters that are moved into the closure

I highlighted the key point saying the references must be valid for 'static which is not the case for me

Yeah, that's why I asked for a complete code sample. Without seeing the trait definition and the calling code and the implementation I can only make more shots in the dark that probably won't help.

1 Like
impl<'a> StoreTrait<'a> for Store {
    fn new_game(
        &self,
        name: &'a str,
        creator_id: &'a str,
        data_supplier_id: &'a str,
        data_supplier_pass: &'a str,
        video_addr: &'a Option<String>,
        auto: bool,
        _cards: &'a Option<Card>,
    ) -> Pin<Box<dyn Future<Output = Result<i32>> + 'a>> {
        let client = self.postgres_client.clone();
        Box::pin(async move {
            client
                .get()
                .await
                .map_err(str_err!(Err::ConnErr))?
                .query_one(
                   "...",
                    &[
                        &creator_id,
                        &name,
                        &data_supplier_id,
                        &pass,
                        &crate::games::GameType::Foo.to_string(),
                        &auto,
                        &video_addr,
                    ],
                )
                .await
                .map_err(|e| {
                    if Some(&SqlState::UNIQUE_VIOLATION) == e.code() {
                        Err::AlreadyExists
                    } else {
                        Err::DBErr(e.to_string())
                    }
                })
                .map(|r| r.get(0))
        })
    }

this is how I implement the thing
and the following is how I call it

#[derive(Clone, Debug, Deserialize)]
struct NewGameParams {
    game_name: String,
    operator_id: String,
    operator_password: String,
    video_addr: Option<String>,
    #[serde(default)]
    auto: bool,
}
#[post("/new")]
async fn new(
    params: Form<NewGameParams>,
    creator: UserFromToken,
    store: web::Data<Store>,
) -> impl Responder {
    if creator.user_type != UserType::SuperAdmin {
        return Err(Err::InsufficientPermissions);
    }
    let NewGameParams {
        game_name,
        operator_id,
        operator_password,
        video_addr,
        auto,
    } = params.0;
    store
        .clone()
        .new_game(
            &game_name,
            &creator.id,
            &operator_id,
            &operator_password,
            &video_addr,
            auto,
            &None,
        )
        .await
        .map(Resp::OkData)
}

@Ixrec

Because of that async move, you do need to be explicit about the lifetimes here. The future itself could be moved across threads, rescheduled, all kinds of things. So, if you are going to do that, lifetimes are what you need. (You're saying "all these references live as long as the future", which is precisely right, I suspect)

There is no way around it, because what's happening under the hood requires it.

2 Likes

exactly.

It compiles so it works I was more looking into approval in this thread considering I never used lifetimes explicitly before thanks :slight_smile:

It's worth noting that Box is doing something special here, too. Specifically, if no lifetimes are specified, Box<dyn Trait> always defaults to Box<dyn Trait + 'static> - meaning it borrows from nothing. If you have a situation where lifetime inference would otherwise work, but it doesn't because of a boxed dyn trait, you can use Box<dyn Trait + '_> to explicitly ask rustc to infer the lifetime.

I believe that won't help in this situation, since rustc refuses to guess which parameters contribute to the result. So if you wrote this:

impl StoreTrait<'_> for Store {
    fn new_game(
        &self,
        name: &str,
        creator_id: &str,
        data_supplier_id: &str,
        data_supplier_pass: &str,
        video_addr: &Option<String>,
        auto: bool,
        _cards: &'a Option<Card>,
    ) -> Pin<Box<dyn Future<Output = Result<i32>> + '_>> {

It would complain anyways about not being able to infer lifetimes.


Regardless, that's why your error without any lifetimes mentions 'static explicitly, rather than just saying "can't infer lifetimes".

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.