问题
I'm making a Discord chat bot using discord-rs, starting from this example. Everything was working and compiling fine until I tried to modify a value that is declared before a loop.
I'm trying to change prefix
to the second word that is entered in a command:
extern crate discord;
use discord::Discord;
use discord::model::Event;
use std::env;
fn main() {
let discord = Discord::from_bot_token(&env::var("DISCORD_TOKEN").expect("Expected token"))
.expect("login failed");
let (mut connection, _) = discord.connect().expect("connect failed");
println!("Ready.");
let mut prefix = "!";
loop {
match connection.recv_event() {
Ok(Event::MessageCreate(message)) => {
if message.content.starts_with(prefix) {
let msg: Vec<&str> = message.content[prefix.len()..].split(" ").collect();
// message.content gets split into separate words and is checked for the command
match msg[0] {
"ag3nprefix" => {
prefix = msg[1];
// ^ here is the line that makes it not compile
let text = format!("AG3N's command prefix changed to: {}", prefix);
let _ = discord.send_message(message.channel_id, &text, "", false);
}
&_ => {}
}
}
}
Ok(_) => {}
Err(discord::Error::Closed(code, body)) => {
println!("Gateway closed on us with code {:?}: {}", code, body);
break;
}
Err(err) => println!("Receive error: {:?}", err),
}
}
}
I tried doing it various ways, but nothing would work. Here's the error from the compiler:
error[E0597]: `message.content` does not live long enough
--> src/main.rs:38:9
|
19 | let msg: Vec<&str> = message.content[prefix.len()..].split(" ").collect();
| --------------- borrow occurs here
...
38 | }
| ^ `message.content` dropped here while still borrowed
39 | }
40 | }
| - borrowed value needs to live until here
回答1:
A smaller example
Here's a MCVE of the problem:
fn main() {
let mut prefix = "!";
let mut i = 0;
loop {
let event = String::from("hello");
match i {
0 => prefix = &event,
_ => println!("{}", prefix),
}
i += 1;
}
}
Rust 2015
error[E0597]: `event` does not live long enough
--> src/main.rs:9:28
|
9 | 0 => prefix = &event,
| ^^^^^ borrowed value does not live long enough
...
14 | }
| - `event` dropped here while still borrowed
15 | }
| - borrowed value needs to live until here
Rust 2018
error[E0597]: `event` does not live long enough
--> src/main.rs:9:27
|
9 | 0 => prefix = &event,
| ^^^^^^ borrowed value does not live long enough
10 | _ => println!("{}", prefix),
| ------ borrow used here, in later iteration of loop
...
14 | }
| - `event` dropped here while still borrowed
The core issue is that event
is dropped at the end of each iteration. However, the code attempts to use prefix
, a reference to event
, in a subsequent iteration. If that were allowed to happen, you'd be accessing invalid memory, causing undefined behavior. Rust disallows this from happening.
You need to change the code so that event
(or the part of it you need) lives longer than any one loop iteration.
Applied to the original code
The golden rule for borrowing problems is to identify who owns a variable. The compiler error messages help you here:
`message.content` does not live long enough
Ok, so we need to look at message.content
or just message
. Who owns that? We've matched a enum and transferred ownership to a local variable called message
, so the variable message
is the owner:
Ok(Event::MessageCreate(message)) => {
The compiler error message agrees, as this error points to the block where message
is in scope (actually the match
braces for technical reasons):
^ `message.content` dropped here while still borrowed
You are trying to take a reference to that string and store the reference somewhere that needs to live longer than a single loop iteration. The compiler has stopped you from introducing memory unsafety into your program. In other languages, you'd write this code and at some point in the future your program would crash (at best) or leak sensitive information or allow injecting code in (at worst).
Instead, allocate a String
inside the loop. Because it has its own allocation, it can live longer than message
. You also need to change the type of the original value of prefix
and change the call to starts_with
:
let mut prefix = "!".to_string();
loop {
match connection.recv_event() {
Ok(Event::MessageCreate(message)) => {
if message.content.starts_with(&prefix) {
let msg: Vec<_> = message.content[prefix.len()..].split(" ").collect();
// message.content gets split into separate words and is checked for the command
match msg[0] {
"ag3nprefix" => {
prefix = msg[1].to_string();
// ^ here is the line that makes it not compile
let text = format!("AG3N's command prefix changed to: {}", prefix);
let _ = discord.send_message(message.channel_id, &text, "", false);
}
&_ => {}
}
}
}
Ok(_) => {}
Err(discord::Error::Closed(code, body)) => {
println!("Gateway closed on us with code {:?}: {}", code, body);
break;
}
Err(err) => println!("Receive error: {:?}", err),
}
}
let _ = discord.send_message(message.channel_id, &text, "", false);
DO NOT IGNORE ERRORS. If you don't want to handle it, just add .expect("I didn't handle this error")
.
来源:https://stackoverflow.com/questions/45664456/value-does-not-live-long-enough-when-trying-to-set-a-variable-outside-a-loop-fro