Consider the following example:
use tokio::task::spawn_blocking;
use std::sync::Arc;
struct X;
impl X {
pub async fn new_x() -> Self {
X
}
pub async fn new_arc() -> Arc<Self> {
Arc::new(X)
}
pub async fn foo1(self: Arc<Self>) {
spawn_blocking(move || self.foo_blocking()).await.expect("spawned task failed")
}
pub async fn foo2(self: &Arc<Self>) {
let this = Arc::clone(self);
spawn_blocking(move || this.foo_blocking()).await.expect("spawned task failed")
}
fn foo_blocking(&self) {
/* potentially blocking */
}
}
struct Y {
inner: Arc<X>,
}
impl Y {
pub async fn new() -> Self {
Y { inner: Arc::new(X) }
}
pub async fn foo3(&self) {
let inner = Arc::clone(&self.inner);
spawn_blocking(move || inner.foo_blocking()).await.expect("spawned task failed")
}
}
#[tokio::main]
async fn main() {
// variants 1 and 2: manually wrap `X` into an `Arc`
let x_manual_arc = Arc::new(X::new_x().await);
x_manual_arc.clone().foo1().await; // variant 1: `foo` consumes `Arc<Self>`
x_manual_arc.foo2().await; // variant 2: `foo` takes `&Arc<Self>`
// variants 3 and 4: make `new` return an `Arc<Self>`
let x_arc = X::new_arc().await; // variants 3 and 4
x_arc.clone().foo1().await; // variant 3: `foo` consumes `Arc<Self>`
x_arc.foo2().await; // variant 4: `foo` consumes `Arc<Self>`
// variant 5: hide `Arc` as an implementation detail by making
// `new` providing a wrapper around a private inner value
let y = Y::new().await;
y.foo3().await;
}
I wonder which of the variants 1 through 5 is the best way to go?
I feel like variant 1 is the most idiomatic one to use because it won't (force) unnecessary creation of Arc
s. But it does feel somewhat unergonomic due to the extra Arc::new
and Arc::clone
needed when calling the foo
method:
let x_manual_arc = Arc::new(X::new_x().await);
x_manual_arc.clone().foo1().await;