Fn method(self : Arc<Self>) is it a good idea?

Hello,

I needed to call another method &mut self via a &mut self method (with thread), but apparently in rust you can't,

struct S {
   t : String;
}

impl S {
   async fn a(&mut self) { // <---------
        tokio::spawn(async move {
            self.b().await;
        });
    }

    async fn b(&mut self) {}
}

13 |      async fn a(&mut self) {
   |            ---------
   |            |
   |            `self` is a reference that is only valid in the method body
   |            let's call the lifetime of this reference `'1`
14 | /         tokio::spawn(async move {
15 | |             self.b().await;
16 | |         });
   | |          ^
   | |          |
   | |__________`self` escapes the method body here
   |            argument requires that `'1` must outlive `'static`

but I was able to thwart the whims of the Borrow checker with this :

Solution :

struct S {
   t : String;
}

impl S {
   async fn a(self: Arc<Self>) { // <---- Arc 
        tokio::spawn(async move {
            self.b().await;
        });
    }

   async fn b(&self) {}
}

but I want the modification too, but Arc<Mutex> is not allowed with Self.

I know that using only "self" will work, but the problem is that the method will take ownership of "self", and I won't be able to use Struct S in the calling code.

async fn a(self) { //<---- with only "self"
        tokio::spawn(async move {
            self.b().await;
        });
    }
#[tokio::main]
async fn main() {
   
 let s = S{t:"Hello".to_string()};
 s.a().await;
  
  // use s here :
  println!("{}",s.t);
  // ....


    match tokio::signal::ctrl_c().await {
        Ok(()) => {
            println!("\nCtl+C detected !");
        }
        Err(err) => {
            eprintln!("Unable to listen for shutdown signal: {}", err);
        }
    }
}

I feel like I'm playing Wrestling/MMA with the Borrow checker :laughing:

Thank you in advance for your help.

This heavily depends on what a and b do, but one thing you can do is change S.

#[derive(Clone)]
struct S {
    t: Arc<Mutex<String>>,
}

Then you can use &self everywhere and clone it when you want to send it to another thread.

You could also leave S alone and wrap it in another struct.

#[derive(Clone)]
struct S2 {
    s: Arc<Mutex<S>>,
}

This allows you to leave b on S, while a is on S2.

Or just have a be an associated function instead of a method.

async fn a(s: Arc<Mutex<Self>>)
3 Likes

Thank you for taking the time to reply :pray: ,
So the best way to do this is to split the Structure into several sub-structures, or expose the fields directly.

I love Rust, but I get the impression that his motto is: "Why make it simple when you can make it complicated?" :laughing:

But I know it's for our own good :smiling_face_with_tear:

What's happening is that Rust requires you to specify how you want the concurrent modifications of state to be handled, by picking where the Mutex goes (or choosing not to have one, and using other approaches such as non-shared state with channels). This is an important choice that, when disregarded and left to happenstance, tends to cause bugs (e.g. different fields getting out of sync with each other, if not actual memory corruption).

5 Likes

Here's a solution:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=82df7c65fdea4af28c32a454183ef030
However, I have to say that this actually working in your usecase is unlikely.

This can only work if you only use self while creating you future, and don't use it inside the future itself.

This is because tokio::spawn requires the future to be 'static, e.g, to be able to live forever. However that future would beed to borrow self, which is only available for a short time.

In this toy example, this works because b doesn't actually do anything, let alone actually use self.

Explanation of the above solution:
To my understanding, the problem has two parts:

  1. In a, the async block is using self. Since we must only use self when creating the future, the future itself can't reference self. So we move the call to b outside.
  2. We have to mark, in b's type, that the future returned from b is not dependant on the lifetime of self.
    To do that, we just desugar the return type to just impl Future<...> instead of impl Future<...> + '_.
1 Like

Thank you for these clarifications :pray:

Your approach is very interesting, I'm going to add it to the list of possible solutions for this kind of problem.

Thanks again.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.