How to get/keep reference after move (spacific case)

I'm new to Rust and trying to learn by developing a Discord bot with serenity, tokio, async, observable pattern and all that good stuff. I've been ganging my had on how to keep having access to an even handler one it is passed on to the serenity client.start() (line 104), so that I can use it later on, once the serenity thread is about to end (line 116) (or even later outside of that thread). I've tried to get an Arc to work, since serenity supports it, but I think I should also use a Mutex. The problem I have (apart from that I'm not exactly sure if that's the right approach), I'm missing some traits implementation in serenity to make it work.
So, here's my full code. Can you please have a look and give me some hints on how to approach this problem? I understand why it doesn't work, but with all that async I'm quite lost on how to make it work. I'd appreciate any help I can get. Thanks in advance!

use serenity::all::ChannelId;
use serenity::async_trait;
use serenity::http::Http;
use serenity::model::{channel::Message, gateway::Ready, guild::Guild};
use serenity::prelude::*;
use std::env;
use std::sync::Arc;
use tokio::io::{self, AsyncBufReadExt, BufReader};

#[derive(Default)]
struct Listener {}

impl Listener {
    fn notify(&self) {
        println!("Notified");
    }
}

struct GetFeed {
    name: String,
    listener: Option<Listener>,
}

#[allow(dead_code)]
impl GetFeed {
    fn new(name: String) -> GetFeed {
        Self {
            name,
            listener: None,
        }
    }

    fn subscribe(&mut self, listener: Listener) {
        self.listener = Some(listener);
    }

    fn unsubscribe(&mut self) {
        self.listener = None;
    }
}

#[async_trait]
impl EventHandler for GetFeed {
    async fn message(&self, ctx: Context, msg: Message) {
        if msg.content == "!ping" {
            if let Err(why) = msg.channel_id.say(&ctx.http, "Pong!").await {
                println!("Error sending message: {why:?}");
            }
        }
    }

    async fn ready(&self, ctx: Context, ready: Ready) {
        let guilds = ctx.cache.guilds().len();

        println!("Guilds in the Cache: {}", guilds);
        for guild in ctx.cache.guilds() {
            println!("Guild name: {}", guild);
        }

        println!("My name is: {}!", self.name);
        println!("{} is connected!", ready.user.name);

        if let Some(listener) = self.listener.as_ref() {
            listener.notify();
        }
    }

    async fn guild_create(&self, _ctx: Context, guild: Guild, _is_new: Option<bool>) {
        println!("Added to: {}", guild.name);
    }
}

async fn say_to_channel(token: &str, channel_id: u64, msg: &str) {
    let channel_id = ChannelId::new(channel_id);
    let http = Http::new(token);
    if let Err(why) = channel_id.say(http, msg).await {
        println!("Error sending message: {why:?}");
    }
}

async fn print_guilds(token: &str) {
    let http = Http::new(token);
    if let Ok(guilds) = http.get_guilds(None, None).await {
        for guild in guilds {
            println!("{}", guild.name);
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
    let channel_id = env::var("DISCORD_CHANNEL_ID")
        .expect("Expected a channel ID in the environment")
        .parse::<u64>()
        .expect("DISCORD Channel ID must be u64 type");
    let bot_name = env::var("DISCORD_BOT_NAME").unwrap_or("GET FEED".to_string());

    let intents = GatewayIntents::all();

    let listener = Listener::default();
    let mut get_feed = GetFeed::new(bot_name);
    get_feed.subscribe(listener);
    let mut client = Client::builder(token.clone(), intents)
        .event_handler_arc(Arc::new(get_feed))
        .await
        .expect("Err creating client");

    let shard_manager = client.shard_manager.clone();

    tokio::spawn(async move {
        if let Err(why) = client.start().await {
            println!("client error: {:?}", why);
        }
        // TODO: do something to make this work
        get_feed.unsubscribe();
    });
    tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;

    println!("Bot is running. Type 'exit' to shut down.");

    let stdin = io::stdin();
    let mut reader = BufReader::new(stdin);
    let mut line = String::new();

    let main = tokio::spawn(async move {
        loop {
            line.clear();
            println!("Enter a command:");
            match reader.read_line(&mut line).await {
                Ok(_) => {
                    if line.trim().starts_with("say:") {
                        if let Some((_, right)) = line.split_once("say:") {
                            say_to_channel(token.as_str(), channel_id, right).await;
                        } else {
                            println!("What?");
                        }
                    } else if line.trim() == "guilds" {
                        print_guilds(token.as_str()).await;
                    } else if line.trim() == "exit" {
                        println!("Shutting down bot...");
                        break;
                    } else {
                        println!("What?");
                    }
                }
                Err(e) => {
                    eprintln!("Failed to read line: {}", e);
                }
            }
        }
        shard_manager.shutdown_all().await;
    });

    let _ = main.await;

    println!("Ok, I'm done.");

    Ok(())
}

EDIT:
The error I'm getting is as follows:

   --> src/main.rs:111:18
    |
102 |     let mut get_feed = GetFeed::new(bot_name);
    |         ------------ move occurs because `get_feed` has type `GetFeed`, which does not implement the `Copy` trait
...
105 |         .event_handler_arc(Arc::new(get_feed))
    |                                     -------- value moved here
...
111 |     tokio::spawn(async move {
    |                  ^^^^^^^^^^ value used here after move
...
116 |         get_feed.unsubscribe();
    |         -------- use occurs due to use in coroutine

Please post the full error message (and code if it's not already there) for the approach you tried.

Ok, updated the original post.

Thanks.

You're correct that you do need an Arc and a Mutex. This is a typical pattern in Rust for shared mutable state and seems to fit here.

In this case you need an Arc around GetFeed so you can call event_handler_arc to share GetFeed among tasks. Be sure to clone the Arc before passing it to that method. Something like this:

    let mut get_feed = Arc::new(GetFeed::new(bot_name));
    get_feed.subscribe(listener);
    let mut client =
        Client::builder(token.clone(), intents)
            .event_handler_arc(get_feed.clone())
            .await
            .expect("Err creating client");

Because serenity treats the handler as read-only but you want to mutate a field in GetFeed you need "interior mutability", which in this case means wrapping the GetFeed::listener field in a Mutex:

    listener: Mutex<Option<Listener>>,

You'll need to lock that Mutex whenever accessing the field using self.listener.lock().unwrap(). This locking should be done inside the methods of GetFeed.

1 Like

Thanks for helping me out. Here's the updated version:

use serenity::all::ChannelId;
use serenity::async_trait;
use serenity::http::Http;
use serenity::model::{channel::Message, gateway::Ready, guild::Guild};
use serenity::prelude::*;
use std::env;
use std::sync::Arc;
use tokio::io::{self, AsyncBufReadExt, BufReader};

#[derive(Default)]
struct Listener {}

impl Listener {
    fn notify(&self) {
        println!("Notified");
    }
}

struct GetFeed {
    name: String,
    listener: Mutex<Option<Listener>>,
}

#[allow(dead_code)]
impl GetFeed {
    fn new(name: String) -> GetFeed {
        Self {
            name,
            listener: Mutex::new(None),
        }
    }

    fn subscribe(&mut self, listener: Listener) {
        self.listener = Mutex::new(Some(listener));
    }

    fn unsubscribe(&mut self) {
        self.listener = Mutex::new(None);
    }
}

#[async_trait]
impl EventHandler for GetFeed {
    async fn message(&self, ctx: Context, msg: Message) {
        if msg.content == "!ping" {
            if let Err(why) = msg.channel_id.say(&ctx.http, "Pong!").await {
                println!("Error sending message: {why:?}");
            }
        }
    }

    async fn ready(&self, ctx: Context, ready: Ready) {
        let guilds = ctx.cache.guilds().len();

        println!("Guilds in the Cache: {}", guilds);
        for guild in ctx.cache.guilds() {
            println!("Guild name: {}", guild);
        }

        println!("My name is: {}!", self.name);
        println!("{} is connected!", ready.user.name);

        if let Some(listener) = self.listener.lock().unwrap() {
            listener.notify();
        }
    }

    async fn guild_create(&self, _ctx: Context, guild: Guild, _is_new: Option<bool>) {
        println!("Added to: {}", guild.name);
    }
}

async fn say_to_channel(token: &str, channel_id: u64, msg: &str) {
    let channel_id = ChannelId::new(channel_id);
    let http = Http::new(token);
    if let Err(why) = channel_id.say(http, msg).await {
        println!("Error sending message: {why:?}");
    }
}

async fn print_guilds(token: &str) {
    let http = Http::new(token);
    if let Ok(guilds) = http.get_guilds(None, None).await {
        for guild in guilds {
            println!("{}", guild.name);
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
    let channel_id = env::var("DISCORD_CHANNEL_ID")
        .expect("Expected a channel ID in the environment")
        .parse::<u64>()
        .expect("DISCORD Channel ID must be u64 type");
    let bot_name = env::var("DISCORD_BOT_NAME").unwrap_or("GET FEED".to_string());

    let intents = GatewayIntents::all();

    let listener = Listener::default();
    let mut get_feed = Arc::new(GetFeed::new(bot_name));
    get_feed.subscribe(listener);
    let mut client = Client::builder(token.clone(), intents)
        .event_handler_arc(get_feed.clone())
        .await
        .expect("Err creating client");

    let shard_manager = client.shard_manager.clone();

    tokio::spawn(async move {
        if let Err(why) = client.start().await {
            println!("client error: {:?}", why);
        }
        // TODO: do something to make this work
        get_feed.unsubscribe();
    });
    tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;

    println!("Bot is running. Type 'exit' to shut down.");

    let stdin = io::stdin();
    let mut reader = BufReader::new(stdin);
    let mut line = String::new();

    let main = tokio::spawn(async move {
        loop {
            line.clear();
            println!("Enter a command:");
            match reader.read_line(&mut line).await {
                Ok(_) => {
                    if line.trim().starts_with("say:") {
                        if let Some((_, right)) = line.split_once("say:") {
                            say_to_channel(token.as_str(), channel_id, right).await;
                        } else {
                            println!("What?");
                        }
                    } else if line.trim() == "guilds" {
                        print_guilds(token.as_str()).await;
                    } else if line.trim() == "exit" {
                        println!("Shutting down bot...");
                        break;
                    } else {
                        println!("What?");
                    }
                }
                Err(e) => {
                    eprintln!("Failed to read line: {}", e);
                }
            }
        }
        shard_manager.shutdown_all().await;
    });

    let _ = main.await;

    println!("Ok, I'm done.");

    Ok(())
}

However, there are many issues now:

error[E0599]: no method named `unwrap` found for opaque type `impl std::future::Future<Output = tokio::sync::MutexGuard<'_, Option<Listener>>>` in the current scope                          
  --> src/main.rs:63:54
   |
63 |         if let Some(listener) = self.listener.lock().unwrap() {
   |                                                      ^^^^^^ method not found in `impl Future<Output = MutexGuard<'_, Option<Listener>>>`
   |
help: consider `await`ing on the `Future` and calling the method on its `Output`
   |
63 |         if let Some(listener) = self.listener.lock().await.unwrap() {
   |                                                      ++++++

error[E0596]: cannot borrow data in an `Arc` as mutable
   --> src/main.rs:116:9
    |
116 |         get_feed.unsubscribe();
    |         ^^^^^^^^ cannot borrow as mutable
    |
    = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Arc<GetFeed>`

warning: variable does not need to be mutable
   --> src/main.rs:102:9
    |
102 |     let mut get_feed = Arc::new(GetFeed::new(bot_name));
    |         ----^^^^^^^^
    |         |
    |         help: remove this `mut`
    |
    = note: `#[warn(unused_mut)]` on by default

error[E0596]: cannot borrow data in an `Arc` as mutable
   --> src/main.rs:103:5
    |
103 |     get_feed.subscribe(listener);
    |     ^^^^^^^^ cannot borrow as mutable
    |
    = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Arc<GetFeed>`

Conceptually, I understand exactly what you're suggesting. But I really struggle to implement this in Rust.

The first thing is that it looks like you're using an async Mutex. Use a sync mutex: std::sync::Mutex. Async Mutexes are for special cases and this is not one of them. In this case we simply want to call the blocking notify method. Or do you intend to make that an async method?

1 Like

Oh, yes, you're right. I've explicitly used std::sync::Mutex now and get the following (different) errors:

  --> src/main.rs:63:16
   |
63 |         if let Some(listener) = self.listener.lock().unwrap() {
   |                ^^^^^^^^^^^^^^   ----------------------------- this expression has type `std::sync::MutexGuard<'_, Option<Listener>>`
   |                |
   |                expected `MutexGuard<'_, Option<Listener>>`, found `Option<_>`
   |
   = note: expected struct `std::sync::MutexGuard<'_, Option<Listener>, >`
                found enum `Option<_>`

error[E0596]: cannot borrow data in an `Arc` as mutable
   --> src/main.rs:116:9
    |
116 |         get_feed.unsubscribe();
    |         ^^^^^^^^ cannot borrow as mutable
    |
    = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Arc<GetFeed>`

warning: variable does not need to be mutable
   --> src/main.rs:102:9
    |
102 |     let mut get_feed = Arc::new(GetFeed::new(bot_name));
    |         ----^^^^^^^^
    |         |
    |         help: remove this `mut`
    |
    = note: `#[warn(unused_mut)]` on by default

error[E0596]: cannot borrow data in an `Arc` as mutable
   --> src/main.rs:103:5
    |
103 |     get_feed.subscribe(listener);
    |     ^^^^^^^^ cannot borrow as mutable
    |
    = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Arc<GetFeed>`

These methods must have &self since they are called from multiple tasks. To mutate a field protected by a mutex you lock it, then change it. The Mutex must not be recreated -- it is the thing accessed by multiple tasks concurrently, and so there must be just one Mutex for the field.

    fn subscribe(&self, listener: Listener) {
        let my_listener = self.listener.lock().unwrap();
        *my_listener = Some(listener);
    }

See the example:

Oh ok. Gotcha. I've made changes and now have the following issues:

rror[E0308]: mismatched types
  --> src/main.rs:67:16
   |
67 |         if let Some(listener) = self.listener.lock().unwrap() {
   |                ^^^^^^^^^^^^^^   ----------------------------- this expression has type `std::sync::MutexGuard<'_, Option<Listener>>`
   |                |
   |                expected `MutexGuard<'_, Option<Listener>>`, found `Option<_>`
   |
   = note: expected struct `std::sync::MutexGuard<'_, Option<Listener>, >`
                found enum `Option<_>`

error[E0596]: cannot borrow data in an `Arc` as mutable
   --> src/main.rs:120:9
    |
120 |         get_feed.unsubscribe();
    |         ^^^^^^^^ cannot borrow as mutable
    |
    = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Arc<GetFeed>`

The whole code looks like this:

use serenity::all::ChannelId;
use serenity::async_trait;
use serenity::http::Http;
use serenity::model::{channel::Message, gateway::Ready, guild::Guild};
use serenity::prelude::*;
use std::env;
use std::sync::Arc;
use tokio::io::{self, AsyncBufReadExt, BufReader};

#[derive(Default)]
struct Listener {}

impl Listener {
    fn notify(&self) {
        println!("Notified");
    }
}

struct GetFeed {
    name: String,
    listener: std::sync::Mutex<Option<Listener>>,
}

#[allow(dead_code)]
impl GetFeed {
    fn new(name: String) -> GetFeed {
        Self {
            name,
            listener: std::sync::Mutex::new(None),
        }
    }

    fn subscribe(&self, listener: Listener) {
        //self.listener = std::sync::Mutex::new(Some(listener));
        let mut my_listener = self.listener.lock().unwrap();
        *my_listener = Some(listener);
    }

    fn unsubscribe(&mut self) {
        //self.listener = std::sync::Mutex::new(None);
        let mut my_listener = self.listener.lock().unwrap();
        *my_listener = None;
    }
}

#[async_trait]
impl EventHandler for GetFeed {
    async fn message(&self, ctx: Context, msg: Message) {
        if msg.content == "!ping" {
            if let Err(why) = msg.channel_id.say(&ctx.http, "Pong!").await {
                println!("Error sending message: {why:?}");
            }
        }
    }

    async fn ready(&self, ctx: Context, ready: Ready) {
        let guilds = ctx.cache.guilds().len();

        println!("Guilds in the Cache: {}", guilds);
        for guild in ctx.cache.guilds() {
            println!("Guild name: {}", guild);
        }

        println!("My name is: {}!", self.name);
        println!("{} is connected!", ready.user.name);

        if let Some(listener) = self.listener.lock().unwrap() {
            listener.notify();
        }
    }

    async fn guild_create(&self, _ctx: Context, guild: Guild, _is_new: Option<bool>) {
        println!("Added to: {}", guild.name);
    }
}

async fn say_to_channel(token: &str, channel_id: u64, msg: &str) {
    let channel_id = ChannelId::new(channel_id);
    let http = Http::new(token);
    if let Err(why) = channel_id.say(http, msg).await {
        println!("Error sending message: {why:?}");
    }
}

async fn print_guilds(token: &str) {
    let http = Http::new(token);
    if let Ok(guilds) = http.get_guilds(None, None).await {
        for guild in guilds {
            println!("{}", guild.name);
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
    let channel_id = env::var("DISCORD_CHANNEL_ID")
        .expect("Expected a channel ID in the environment")
        .parse::<u64>()
        .expect("DISCORD Channel ID must be u64 type");
    let bot_name = env::var("DISCORD_BOT_NAME").unwrap_or("GET FEED".to_string());

    let intents = GatewayIntents::all();

    let listener = Listener::default();
    let mut get_feed = Arc::new(GetFeed::new(bot_name));
    get_feed.subscribe(listener);
    let mut client = Client::builder(token.clone(), intents)
        .event_handler_arc(get_feed.clone())
        .await
        .expect("Err creating client");

    let shard_manager = client.shard_manager.clone();

    tokio::spawn(async move {
        if let Err(why) = client.start().await {
            println!("client error: {:?}", why);
        }
        // TODO: do something to make this work
        get_feed.unsubscribe();
    });
    tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;

    println!("Bot is running. Type 'exit' to shut down.");

    let stdin = io::stdin();
    let mut reader = BufReader::new(stdin);
    let mut line = String::new();

    let main = tokio::spawn(async move {
        loop {
            line.clear();
            println!("Enter a command:");
            match reader.read_line(&mut line).await {
                Ok(_) => {
                    if line.trim().starts_with("say:") {
                        if let Some((_, right)) = line.split_once("say:") {
                            say_to_channel(token.as_str(), channel_id, right).await;
                        } else {
                            println!("What?");
                        }
                    } else if line.trim() == "guilds" {
                        print_guilds(token.as_str()).await;
                    } else if line.trim() == "exit" {
                        println!("Shutting down bot...");
                        break;
                    } else {
                        println!("What?");
                    }
                }
                Err(e) => {
                    eprintln!("Failed to read line: {}", e);
                }
            }
        }
        shard_manager.shutdown_all().await;
    });

    let _ = main.await;

    println!("Ok, I'm done.");

    Ok(())
}

When you lock a mutex it returns a lock guard. That guard object can be dereferenced (explicitly or implicitly) to access its value (the listener). You can reference it mutably or immutably.

        let mut listener = self.listener.lock().unwrap();
        if let Some(listener) = &mut *listener {
            listener.notify();
        }

Here we need to declare the lock guard separately and keep it in scope while we access it, since we're accessing its value in multiple statements.

I see. Ok, so now I only have the last issue - accessing the handler after it was moved:

error[E0596]: cannot borrow data in an `Arc` as mutable
   --> src/main.rs:124:9
    |
124 |         get_feed.unsubscribe();
    |         ^^^^^^^^ cannot borrow as mutable
    |
    = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Arc<GetFeed>`

Full code looks like this now:

use serenity::all::ChannelId;
use serenity::async_trait;
use serenity::http::Http;
use serenity::model::{channel::Message, gateway::Ready, guild::Guild};
use serenity::prelude::*;
use std::env;
use std::sync::Arc;
use tokio::io::{self, AsyncBufReadExt, BufReader};

#[derive(Default)]
struct Listener {}

impl Listener {
    fn notify(&self) {
        println!("Notified");
    }
}

struct GetFeed {
    name: String,
    listener: std::sync::Mutex<Option<Listener>>,
}

#[allow(dead_code)]
impl GetFeed {
    fn new(name: String) -> GetFeed {
        Self {
            name,
            listener: std::sync::Mutex::new(None),
        }
    }

    fn subscribe(&self, listener: Listener) {
        //self.listener = std::sync::Mutex::new(Some(listener));
        let mut my_listener = self.listener.lock().unwrap();
        *my_listener = Some(listener);
    }

    fn unsubscribe(&mut self) {
        //self.listener = std::sync::Mutex::new(None);
        let mut my_listener = self.listener.lock().unwrap();
        *my_listener = None;
    }
}

#[async_trait]
impl EventHandler for GetFeed {
    async fn message(&self, ctx: Context, msg: Message) {
        if msg.content == "!ping" {
            if let Err(why) = msg.channel_id.say(&ctx.http, "Pong!").await {
                println!("Error sending message: {why:?}");
            }
        }
    }

    async fn ready(&self, ctx: Context, ready: Ready) {
        let guilds = ctx.cache.guilds().len();

        println!("Guilds in the Cache: {}", guilds);
        for guild in ctx.cache.guilds() {
            println!("Guild name: {}", guild);
        }

        println!("My name is: {}!", self.name);
        println!("{} is connected!", ready.user.name);

        //if let Some(listener) = self.listener.lock().unwrap() {
        //    listener.notify();
        //}
        let mut listener = self.listener.lock().unwrap();
        if let Some(listener) = &mut *listener {
            listener.notify();
        }
    }

    async fn guild_create(&self, _ctx: Context, guild: Guild, _is_new: Option<bool>) {
        println!("Added to: {}", guild.name);
    }
}

async fn say_to_channel(token: &str, channel_id: u64, msg: &str) {
    let channel_id = ChannelId::new(channel_id);
    let http = Http::new(token);
    if let Err(why) = channel_id.say(http, msg).await {
        println!("Error sending message: {why:?}");
    }
}

async fn print_guilds(token: &str) {
    let http = Http::new(token);
    if let Ok(guilds) = http.get_guilds(None, None).await {
        for guild in guilds {
            println!("{}", guild.name);
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
    let channel_id = env::var("DISCORD_CHANNEL_ID")
        .expect("Expected a channel ID in the environment")
        .parse::<u64>()
        .expect("DISCORD Channel ID must be u64 type");
    let bot_name = env::var("DISCORD_BOT_NAME").unwrap_or("GET FEED".to_string());

    let intents = GatewayIntents::all();

    let listener = Listener::default();
    let mut get_feed = Arc::new(GetFeed::new(bot_name));
    get_feed.subscribe(listener);
    let mut client = Client::builder(token.clone(), intents)
        .event_handler_arc(get_feed.clone())
        .await
        .expect("Err creating client");

    let shard_manager = client.shard_manager.clone();

    tokio::spawn(async move {
        if let Err(why) = client.start().await {
            println!("client error: {:?}", why);
        }
        // TODO: do something to make this work
        get_feed.unsubscribe();
    });
    tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;

    println!("Bot is running. Type 'exit' to shut down.");

    let stdin = io::stdin();
    let mut reader = BufReader::new(stdin);
    let mut line = String::new();

    let main = tokio::spawn(async move {
        loop {
            line.clear();
            println!("Enter a command:");
            match reader.read_line(&mut line).await {
                Ok(_) => {
                    if line.trim().starts_with("say:") {
                        if let Some((_, right)) = line.split_once("say:") {
                            say_to_channel(token.as_str(), channel_id, right).await;
                        } else {
                            println!("What?");
                        }
                    } else if line.trim() == "guilds" {
                        print_guilds(token.as_str()).await;
                    } else if line.trim() == "exit" {
                        println!("Shutting down bot...");
                        break;
                    } else {
                        println!("What?");
                    }
                }
                Err(e) => {
                    eprintln!("Failed to read line: {}", e);
                }
            }
        }
        shard_manager.shutdown_all().await;
    });

    let _ = main.await;

    println!("Ok, I'm done.");

    Ok(())
}

I'll try to show how you can debug this. Only immutable access is allowed through an Arc. The message is telling us that get_feed is being accessed mutably. If the unsubscribe method has a &self param, then it should be accessed immutably. So do you see why the compiler thinks it is being accessed mutably?

I thought it was because I was declaring it with mut:

let mut get_feed = Arc::new(GetFeed::new(bot_name));

But I removed that and I still had the same issue. So, does it have something to do with me spawning and moving here: tokio::spawn?

tokio::spawn(async move {

It's that unsubscribe is declared with a &mut self param.

1 Like

Oh damn, I was sure I've updated it, sorry completely missed it. It works ok now, thanks for helping out!
I'm gonna now review it to make sure I fully get how to juggle those arcs and mutexes.
One last question, I pass my Arc as clone to the serenity client. But this does only a clone of the reference, not the whole object, doesn't? Which means under the hoods I'm still operating on the original GetFeed?

1 Like

Yes, this is indeed the case. In this case, since GetFeed itself doesn’t implement the Clone trait anyway, it’s impossible it duplicates the GetFeed object instead.

Still, a common pattern to make this (cloning of Arcs) more explicit is to write out the method call as a function instead:

        .event_handler_arc(Arc::clone(&get_feed))

instead of

        .event_handler_arc(get_feed.clone())

This makes it impossible to accidentally clone an object when you intended to share it via cloned Arc, especially in other situations where you might have an object behind the Arc that’s itself cloneable, and when it’s a bit obscure where in your code you’re working with Arc<T> and what other variables might be &T instead.

3 Likes

Sure. Please go ahead and select one of my replies as the solution using the check mark at the bottom.

Yes, and that's what you wanted.

1 Like

Thanks for the hint!