Tokio subtask within task not executing as expected - asynchronous

Although I am new to rust, after having read the rust and tokio books I thought to know how async tasks work. However, obviously I missed something crucial, since I can't seem to be able to resolve the following problem:
I have an async function (asfunc) that needs to poll some sensors indefinitely. For the purposes of demonstration and testing, I created the following stylized function emulating a steady source:
async fn asfunc(tx: Sender<String>, name: String) {
loop {
thread::sleep( time::Duration::from_secs(1));
let now = time::Instant::now();
tx.send(format!("[{}] {:?}",name, now)).unwrap();
}
}
I will have to run several of these concurrently form within a main program, and some of these async function will be wrapped in another async function (wrapper()) for post-processing of the data received. Below is the core functionality (it's in a file called th_test.rs):
use std::sync::mpsc::Sender;
use std::{thread, time};
async fn wrapper() {
let (tx, rx) = std::sync::mpsc::channel::<String>();
tokio::task::spawn(async move {
asfunc(tx,"two".to_string()).await;
});
while let Ok(msg) = rx.recv() {
println!("[wrapper] {:?}",msg);
}
}
async fn asfunc(tx: Sender<String>, name: String) {
loop {
thread::sleep( time::Duration::from_secs(1));
let now = time::Instant::now();
tx.send(format!("[{}] {:?}",name, now)).unwrap();
}
}
pub async fn run_test() {
let (tx, rx) = std::sync::mpsc::channel::<String>();
tokio::task::spawn(async move {
asfunc(tx,"one".to_string()).await;
});
tokio::task::spawn(async move {
wrapper().await;
});
while let Ok(msg) = rx.recv() {
println!("[asfunc] {:?}",msg);
}
}
And for completeness sake, here is the main.rs file:
mod th_test;
#[tokio::main]
async fn main() {
th_test::run_test().await;
}
When I run this, I would expect some output from the wrapper function as well, since the code is pretty much identical to what I have in the run_test() function, but what I get is only the output from "directly" calling asfunc:
[asfunc] [one] Instant { t: 797790.6589759s }
[asfunc] [one] Instant { t: 797791.6660039s }
[asfunc] [one] Instant { t: 797792.678952s }
...
What am doing wrong here? Any help would be greatly appreciated!

Never use thread::sleep() or std::sync::mpsc channels (or any other blocking operation) inside async fn. Use the corresponding async primitives: tokio::time::sleep() and tokio::sync::mpsc. With them, your code works.
The exact reason it fails is not clear to me: probably something somewhen blocks all executor threads and it cannot run the task wrapper() spawned (I can confirm it doesn't run with a simple println!()). But it doesn't matter: you should never block the executor. See Async: What is blocking?.

I fully agree with everything #ChayimFriedman says.
Here is some code to accompany his answer, based on the fact that you say that you need to use std::sync::mpsc:
use std::sync::mpsc::Sender;
use std::time;
async fn wrapper() {
let (tx, rx) = std::sync::mpsc::channel::<String>();
tokio::task::spawn(async move {
asfunc(tx, "two".to_string()).await;
});
tokio::task::spawn_blocking(move || {
while let Ok(msg) = rx.recv() {
println!("[wrapper] {:?}", msg);
}
})
.await
.unwrap();
}
async fn asfunc(tx: Sender<String>, name: String) {
loop {
tokio::time::sleep(time::Duration::from_secs(1)).await;
let now = time::Instant::now();
tx.send(format!("[{}] {:?}", name, now)).unwrap();
}
}
pub async fn run_test() {
let (tx, rx) = std::sync::mpsc::channel::<String>();
tokio::task::spawn(async move {
asfunc(tx, "one".to_string()).await;
});
tokio::task::spawn(async move {
wrapper().await;
});
tokio::task::spawn_blocking(move || {
while let Ok(msg) = rx.recv() {
println!("[asfunc] {:?}", msg);
}
})
.await
.unwrap();
}
#[tokio::main]
async fn main() {
run_test().await;
}
[wrapper] "[two] Instant { tv_sec: 100182, tv_nsec: 4500700 }"
[asfunc] "[one] Instant { tv_sec: 100182, tv_nsec: 4714700 }"
[wrapper] "[two] Instant { tv_sec: 100183, tv_nsec: 5557800 }"
[asfunc] "[one] Instant { tv_sec: 100183, tv_nsec: 5976800 }"
[wrapper] "[two] Instant { tv_sec: 100184, tv_nsec: 7570800 }"
[asfunc] "[one] Instant { tv_sec: 100184, tv_nsec: 7329700 }"
[wrapper] "[two] Instant { tv_sec: 100185, tv_nsec: 8777300 }"
[asfunc] "[one] Instant { tv_sec: 100185, tv_nsec: 9211100 }"
[asfunc] "[one] Instant { tv_sec: 100186, tv_nsec: 11582400 }"
[wrapper] "[two] Instant { tv_sec: 100186, tv_nsec: 11741700 }"
[wrapper] "[two] Instant { tv_sec: 100187, tv_nsec: 13959800 }"
...

Related

rust futures::select modifying local variable in loop

I have a struct G like this
struct G { /* some member */ }
impl G {
async fn ref_foo(&self) { /* some code uses G's member */ }
async fn mut_foo(&mut self) { /* some code modifies G's member */ }
}
which is responsible to handle requests from a mpsc::Receiver, like this -- which won't compile:
async fn run_loop(mut rx: impl Stream<Item = Task> + FusedStream + Unpin) {
let mut g = G {};
let mut QS = FuturesUnordered::new();
loop {
select! {
t = rx.select_next_some() => match t {
TR {..} => QS.push(g.ref_foo()),
TM {..} => QS.push(g.mut_foo()),
},
_ = QS.select_next_some() => {},
}
}
}
the above code won't compile due to multiple mutable reference to g.
Target:
What I want is that the loop runs in parallel for any number of ref_foo tasks, and when it needs to run a mut_foo task, it waits until every ref_foo task finish, and then run the mut_foo task, then it can run other tasks as usual.
/ g.ref_foo() \ / ...
| g.ref_foo() | | ...
g.mut_foo() => < g.ref_foo() > => g.mut_foo() => g.mut_foo() => < ...
| g.ref_foo() | | ...
\ g.ref_foo() / \ ...
Additional Infomation:
I used to move implementation of mut_foo to the select loop, and remove async on g.mut_foo() so that no mutable reference would be used in stream QS.
But this implementation is really cubersome and undoubtedly broke G's design.
Just now, I come up with another implementation by make a wrapper:
async fn run_task(mut g: G, t: Task) -> G {
match t {
TR {..} => g.ref_foo().await,
TM {..} => g.mut_foo().await,
};
g
}
while in the select loop:
async fn run_loop(mut rx: impl Stream<Item = Task> + FusedStream + Unpin) {
let g0 = G {};
let mut QS = FuturesUnordered::new();
let mut getter = FuturesUnordered::new();
getter.push(ready(g0));
loop {
select! {
t = rx.select_next_some() => {
let mut g = getter.select_next_some().await;
QS.push(run_task(g, t));
},
mut g = QS.select_next_some() => getter.push(ready(g)),
}
}
}
this one compiles, but it's not so "async" as it can possibly be. In this implementation, ref_foo tasks are also running sequentially.
Question:
Are there more material I should learn to solve this problem? The technics I'm using comes from rust-async-book
Do I HAVE TO use RefCell to solve this problem? IMHO, this should be a trivial problem that can be solved without breaking rust's borrowing rules (by using RefCell).
Can I change my wrap run_task and the select loop so that ref_foo runs in parallel? I have problem in the implementation because G is flowing getter => QS => getter => ..., there's no long-term G instance, and I cannot figure out where I can store it.
Append some of my thoughts:
Since mut_foo can not be run in parallel, I am trying to solve this problem by removing async keyword on mut_foo -- with little progress. The core problem is that, immutable ref to G is needed for parallel running of ref_foo, but I have to get rid of all these immutable ref G when it's time for mut_foo. The fact do not change whether mut_foo is async or not ( or "whether mut_foo returns ref G or not").
I've solved Question 3 with a lot of if statements. I hope there are some more elegant implementations. And, I really appreciate any learning material as stated in Question 1.
here's the full code that compiles (simplified):
use tokio::runtime;
use std::thread;
use std::time::Duration;
use futures::{
select, StreamExt, SinkExt,
future::{ready},
stream::{FusedStream, FuturesUnordered, Stream},
};
struct G;
impl G {
async fn ref_foo(&self) { println!("ref_foo +++"); tokio::time::sleep(Duration::from_millis(500)).await; println!("ref_foo ---"); }
async fn mut_foo(&mut self) { println!("mut_foo +++"); tokio::time::sleep(Duration::from_millis(500)).await; println!("mut_foo ---"); }
}
#[derive(Clone)]
enum Task {
TR,
TM,
}
// wrappers
async fn run_ref_task(g: &G, task: Task) {
match task {
Task::TR => g.ref_foo().await,
_ => {},
};
}
async fn run_mut_task(mut g: G, task: Task) -> G {
match task {
Task::TM => g.mut_foo().await,
_ => {},
};
g
}
async fn run_loop(mut rx: impl Stream<Item = Task> + FusedStream + Unpin) {
let g0 = G;
let mut getter = FuturesUnordered::new();
getter.push(ready(g0));
// the following streams stores only `ready(task)`
let mut mut_tasks = FuturesUnordered::new(); // for tasks that's scheduled in this loop
let mut ref_tasks = FuturesUnordered::new();
let mut mut_delay = FuturesUnordered::new(); // for tasks that's scheduled in next loop
let mut ref_delay = FuturesUnordered::new();
loop {
println!("============ avoid idle loops ============");
let g = getter.select_next_some().await;
{
let mut queries = FuturesUnordered::new(); // where we schedule ref_foo tasks
loop {
println!("------------ avoid idle ref_task loops ------------");
select! {
task = rx.select_next_some() => {
match &task {
Task::TR => ref_delay.push(ready(task)),
Task::TM => mut_tasks.push(ready(task)),
};
if mut_delay.is_empty() && ref_tasks.is_empty() && queries.is_empty() { break; }
},
task = mut_delay.select_next_some() => {
mut_tasks.push(ready(task));
if mut_delay.is_empty() && ref_tasks.is_empty() && queries.is_empty() { break; }
}
task = ref_tasks.select_next_some() => {
queries.push(run_ref_task(&g, task));
}
_ = queries.select_next_some() => {
if mut_delay.is_empty() && ref_tasks.is_empty() && queries.is_empty() { break; }
},
}
}
}
getter.push(ready(g));
{
let mut queries = FuturesUnordered::new(); // where we schedule mut_foo tasks
loop {
println!("------------ avoid idle mut_task loops ------------");
select! {
task = rx.select_next_some() => {
match &task {
Task::TR => ref_tasks.push(ready(task)),
Task::TM => mut_delay.push(ready(task)),
};
if ref_delay.is_empty() && mut_tasks.is_empty() && queries.is_empty() { break; }
},
task = ref_delay.select_next_some() => {
ref_tasks.push(ready(task));
if ref_delay.is_empty() && mut_tasks.is_empty() && queries.is_empty() { break; }
}
g = getter.select_next_some() => {
if let Some(task) = mut_tasks.next().await {
queries.push(run_mut_task(g, task));
} else {
getter.push(ready(g));
if ref_delay.is_empty() && queries.is_empty() { break; }
}
}
g = queries.select_next_some() => {
getter.push(ready(g));
if ref_delay.is_empty() && mut_tasks.is_empty() && queries.is_empty() { break; }
}
}
}
}
}
}
fn main() {
let (mut tx, rx) = futures::channel::mpsc::channel(10000);
let th = thread::spawn(move || thread_main(rx));
let tasks = vec![Task::TR, Task::TR, Task::TM, Task::TM, Task::TR, Task::TR, Task::TR, Task::TM, Task::TM];
let rt = runtime::Builder::new_multi_thread().enable_time().build().unwrap();
rt.block_on(async {
loop {
for task in tasks.clone() {
tx.send(task).await.expect("");
}
tokio::time::sleep(Duration::from_secs(10)).await;
}
});
th.join().expect("");
}
fn thread_main(rx: futures::channel::mpsc::Receiver<Task>) {
let rt = runtime::Builder::new_multi_thread().enable_time().build().unwrap();
rt.block_on(async {
run_loop(rx).await;
});
}

Cannot infer an appropriate lifetime for self "required to be static"

I am prototyping channel based system and when converting code to use async tasks, I came across the error that &mut self needs to have an appropriate lifetime.
I have tried setting the &mut self to &'static self but that does not work. I have tried to wrap the entire code in an async block returning Future<Output=()> + 'static which also did not work.
Here is the code:
struct RequestManager {
connection_state: ConnectionState,
backoff: u64,
exponent: u32,
maximum: u64,
}
impl RequestManager {
async fn run(&mut self, mut feed_queue: Receiver<FeedItem>) {
let (mut fetch_result_sender, mut fetch_result_receiver) = channel(5);
let (mut fetch_request_sender, mut fetch_request_receiver) = channel::<FeedItem>(5);
let request_sender = mpsc::Sender::clone(&fetch_result_sender);
tokio::spawn(async move {
loop {
match self.connection_state {
ConnectionState::Closed => {
while let Some(feed) = fetch_request_receiver.recv().await {
let mut request_sender = mpsc::Sender::clone(&request_sender);
tokio::spawn(async move {
let response = make_request(&feed.feed_address).await;
if let Err(_) = response {
self.connection_state =
ConnectionState::HalfOpen(feed.feed_address);
} else {
let response = read_response_body(response.unwrap()).await;
let result = FetchResult { body: response };
if let Err(e) = request_sender.send(result).await {
eprintln!("could not send fetch result: {}", e);
}
}
});
}
}
ConnectionState::HalfOpen(url) => {
let response = make_request(&url).await;
if let Err(_) = response {
self.connection_state = ConnectionState::Open(url);
} else {
let response = read_response_body(response.unwrap()).await;
let result = FetchResult { body: response };
// // TODO: sends to task/feedService
connection_state = ConnectionState::Closed;
if let Err(e) = fetch_result_sender.send(result).await {
eprintln!("could not send fetch result: {}", e);
}
}
}
ConnectionState::Open(url) => {
let new_backoff = calculate_backoff();
delay_for(Duration::from_secs(new_backoff));
self.connection_state = ConnectionState::HalfOpen(url)
}
}
}
});
}
}

Searching a Vec for a match

My first Rust program compiles and runs:
use structopt::StructOpt;
use pcap::{Device,Capture};
use std::process::exit;
#[derive(StructOpt)]
struct Cli {
/// the capture device
device: String,
}
fn main() {
let devices = Device::list();
let args = Cli::from_args();
let mut optdev :Option<Device> = None;
for d in devices.unwrap() {
//println!("device: {:?}", d);
if d.name == args.device {
optdev = Some(d);
}
}
let dev = match optdev {
None => {
println!("Device {} not found.", args.device);
exit(1);
},
Some(dev) => dev,
};
let mut cap = Capture::from_device(dev).unwrap()
.promisc(true)
.snaplen(100)
.open().unwrap();
while let Ok(packet) = cap.next() {
println!("received packet! {:?}", packet);
}
}
I have some complex code which iterates through the Vec of devices, testing each one's .name property against args.device.
I'm guessing that there is a method of 'looking-up' an entry in a Vec, such that I can replace all the optdev lines with something like:
let dev = match devices.unwrap().look_up(.name == args.device) {
None => {
println!("Device {} not found.", args.device);
exit(1);
},
Some(dev) => dev,
};
What is the syntax for such a look_up()?
Or is there a more idiomatic way of doing this?
What is the syntax for such a look_up()?
Iterator::find. Since the operation is not specific to vectors (or slices), it doesn't live there, and is applicable to any iterator instead.
It'd look something like this:
let dev = match devices.unwrap().into_iter().find(|d| d.name == args.device) {
None => {
println!("Device {} not found.", args.device);
exit(1);
},
Some(dev) => dev,
};
or
let dev = if let Some(dev) = devices.unwrap().into_iter().find(|d| d.name == args.device) {
dev
} else {
println!("Device {} not found.", args.device);
exit(1);
};
(side-note: you may also want to use eprintln for, well, error reporting).
Though a somewhat cleaner error handling could be along the lines of (note: not tested so there might be semantic or syntactic mistakes):
use std::fmt;
use std:errors::Error;
#[derive(Debug)]
struct NoDevice(String);
impl fmt::Display for NoDevice {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Device {} not found", self.0)
}
}
impl Error for NoDevice {}
fn main() -> Result<(), Box<dyn Error>> {
let devices = Device::list()?;
let args = Cli::from_args();
let dev = devices.into_iter()
.find(|d| d.name == args.device)
.ok_or_else(|| NoDevice(args.device))?
let mut cap = Capture::from_device(dev)?
.promisc(true)
.snaplen(100)
.open()?;
while let Ok(packet) = cap.next() {
println!("received packet! {:?}", packet);
}
}

Tokio FramedRead.for_each called indefinitely for single response

I've been mucking with tokio for a few weeks in the pursuit of writing a protocol using tokio_uds. There are several issues with the following code:
framed.for_each is called over and over from a single response.
The socket only sends 1 real message, but the Decoder decodes the exact same event as many times as it can until it fills up the bounded channel.
Nothing is ever received over the channel (rx.for_each never prints anything), though it appears to be written until it fills up.
I need to use a UnixStream and not a UnixListener because there's some data I must put over the socket first to 'subscribe' to the service and let it know what to send.
use byteorder::{ByteOrder, LittleEndian};
use bytes::{Buf, BufMut, Bytes, BytesMut, IntoBuf};
use futures::prelude::*;
use futures::sync::mpsc::{self, Receiver, Sender};
use futures::Stream;
use tokio::prelude::*;
use tokio_codec::{Decoder, Encoder, FramedRead};
use tokio_uds::UnixStream;
fn subscribe(tx: Sender<event::Evt>, events: Vec<Event>) -> io::Result<()> {
let fut = UnixStream::connect(socket_path()?)
.and_then(move |stream| {
// some setup
tokio::io::write_all(stream, buf)
})
.and_then(|(stream, _buf)| {
let buf = [0_u8; 30]; // <i3-ipc (6 bytes)><len (4 bytes)><type (4 bytes)><{success:true} 16 bytes>
tokio::io::read_exact(stream, buf)
})
.and_then(|(stream, initial)| {
if &initial[0..6] != MAGIC.as_bytes() {
panic!("Magic str not received");
}
// decoding initial response and returning stream
future::ok(stream)
})
.and_then(move |stream| {
let framed = FramedRead::new(stream, EvtCodec);
let sender = framed
.for_each(move |evt| {
let tx = tx.clone();
tx.send(evt).wait(); // this line is called continuously until buffer fills
Ok(())
})
.map_err(|err| println!("{}", err));
tokio::spawn(sender);
Ok(())
})
.map(|_| ())
.map_err(|e| eprintln!("{:?}", e));
tokio::run(fut);
Ok(())
}
fn test_sub() -> io::Result<()> {
let (tx, rx) = mpsc::channel(5);
subscribe(tx, vec![Event::Window])?;
let fut = rx.for_each(|e: event::Evt| {
println!("received"); // never reaches
future::ok(())
});
tokio::spawn(fut);
Ok(())
}
My Decoder:
pub struct EvtCodec;
/// decoding: "<i3-ipc><payload len: u32><msg type: u32><payload>"
impl Decoder for EvtCodec {
type Item = event::Evt;
type Error = io::Error;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, io::Error> {
if src.len() > 14 {
if &src[0..6] != MAGIC.as_bytes() {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("Expected 'i3-ipc' but received: {:?}", &src[0..6]),
));
}
let payload_len = LittleEndian::read_u32(&src[6..10]) as usize;
let evt_type = LittleEndian::read_u32(&src[10..14]);
dbg!(&src.len()); // 878
dbg!(payload_len); // 864
if src.len() < 14 + payload_len {
Ok(None)
} else {
let evt = decode_evt(evt_type, src[14..].as_mut().to_vec())?;
dbg!(&evt); // correctly prints out a well-formed event
Ok(Some(evt))
}
} else {
Ok(None)
}
}
}
I saw that you resolved your other issue, and I'd be really interested to see how you solved this problem. Here's how I fixed it on my TCP Tokio side project:
use byteorder::{ByteOrder, LittleEndian};
use bytes::{Buf, BufMut, Bytes, BytesMut, IntoBuf};
use futures::prelude::*;
use futures::sync::mpsc::{self, Receiver, Sender};
use futures::Stream;
use tokio::prelude::*;
use tokio_codec::{Decoder, Encoder, FramedRead};
use tokio_uds::UnixStream;
fn subscribe(tx: Sender<event::Evt>, rx: Receiver<event::Evt>, events: Vec<Event>) -> io::Result<()> {
let fut = UnixStream::connect(socket_path()?)
.and_then(move |stream| {
// some setup
tokio::io::write_all(stream, buf)
})
.and_then(|(stream, _buf)| {
let buf = [0_u8; 30]; // <i3-ipc (6 bytes)><len (4 bytes)><type (4 bytes)><{success:true} 16 bytes>
tokio::io::read_exact(stream, buf)
})
.and_then(|(stream, initial)| {
if &initial[0..6] != MAGIC.as_bytes() {
panic!("Magic str not received");
}
// decoding initial response and returning stream
future::ok(stream)
})
.and_then(move |stream| {
let framed = FramedRead::new(stream, EvtCodec);
let (writer, reader) = framed.split();
// Connect your framed reader to the channel
let sink = rx.forward(writer.sink_map_err(|_| ()));
tokio::spawn(sink.map(|_| ()));
let sender = reader
.for_each(move |evt| {
let tx = tx.clone();
tx.send(evt).wait(); // this line is called continuously until buffer fills
Ok(())
})
.map_err(|err| println!("{}", err));
tokio::spawn(sender);
Ok(())
})
.map(|_| ())
.map_err(|e| eprintln!("{:?}", e));
tokio::run(fut);
Ok(())
}
fn test_sub() -> io::Result<()> {
let (tx, rx) = mpsc::channel(5);
subscribe(tx, rx, vec![Event::Window])?;
let fut = rx.for_each(|e: event::Evt| {
println!("received"); // never reaches
future::ok(())
});
tokio::spawn(fut);
Ok(())
}
And the Decoder with the buffer clear:
pub struct EvtCodec;
/// decoding: "<i3-ipc><payload len: u32><msg type: u32><payload>"
impl Decoder for EvtCodec {
type Item = event::Evt;
type Error = io::Error;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, io::Error> {
if src.len() > 14 {
if &src[0..6] != MAGIC.as_bytes() {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("Expected 'i3-ipc' but received: {:?}", &src[0..6]),
));
}
let payload_len = LittleEndian::read_u32(&src[6..10]) as usize;
let evt_type = LittleEndian::read_u32(&src[10..14]);
dbg!(&src.len()); // 878
dbg!(payload_len); // 864
if src.len() < 14 + payload_len {
Ok(None)
} else {
let evt = decode_evt(evt_type, src[14..].as_mut().to_vec())?;
dbg!(&evt); // correctly prints out a well-formed event
src.clear(); // Clears the buffer, so you don't have to keep decoding the same packet over and over.
Ok(Some(evt))
}
} else {
Ok(None)
}
}
}
Hope this helps!
EDIT:
According to a user on the rust subreddit that commented after I included this solution in a blog post, src.clear() is probably the wrong answer for me. I should instead be using `src.advance(14+payload_len)
linking the reddit comment here

modifying a field while pattern matching on it

I tried my hands at Rust for the first time today (writing a XML tokenizer), and naturally don’t understand everything:
I have a struct with field that can take an enum value:
enum State { Outside, InATag(~str) }
struct Tokenizer { state: State }
In a impl Tokenizer, I want to match on the current state, and change it in some cases, however this always gives a use of moved value error.
H to access and/or declare the state field so that I can match on it and change its value inside a match branch?
Sorry for the confusion, I meant to change the Tokenizer’s state field, not the state’s String field!
match self.state {
InATag(name) => self.state = Outside,
Outside => ()
}
Without a more concrete example, it is hard to tell whether this would solve your problem, but you can use ref within a match pattern to make a reference to the matched substructure, and you can use ref mut to make that reference mutable.
So, in your example:
enum State { Outside, InATag(~str) }
struct Tokenizer { state: State }
fn main() {
let mut t = Tokenizer { state: InATag(~"foo") };
match t.state {
InATag(ref mut _s) => { *_s = ~"bar"; }
Outside => { /* impossible */ }
}
io::println(fmt!("Hello World: %?", t));
}
or if you need to match other parts of the Tokenizer state, this works too:
fn main() {
let mut t = Tokenizer { state: InATag(~"foo") };
match t {
Tokenizer { state: InATag(ref mut _s) } => { *_s = ~"bar"; }
Tokenizer { state: Outside } => { /* impossible */ }
}
io::println(fmt!("Hello World: %?", t));
}
Note that when doing this sort of code, it can be pretty easy to inadvertently run into borrow-check violations due to aliasing. For example, here is a relatively small change to the second example above that won't compile:
fn main() {
let mut t = Tokenizer { state: InATag(~"foo") };
match &t {
&Tokenizer { state: InATag(ref mut _s) } => { *_s = ~"bar"; }
&Tokenizer { state: Outside } => { /* impossible */ }
}
io::println(fmt!("Hello World: %?", t));
}
causes the following message from rustc:
/tmp/m.rs:7:35: 7:46 error: illegal borrow: creating mutable alias to enum content
/tmp/m.rs:7 &Tokenizer { state: InATag(ref mut _s) } => { *_s = ~"bar"; }
^~~~~~~~~~~
error: aborting due to previous error
because you do not want an outstanding borrow &t while you are also creating mutable aliases to the internals of t
Okay, with the clarified variant of the question, here is my revised answer:
enum State { Outside, InATag(~str) }
struct Tokenizer { state: State }
impl Tokenizer {
fn toggle(&mut self) {
match self {
&Tokenizer { state: InATag(*) } => { self.state = Outside }
&Tokenizer { state: Outside } => { self.state = InATag(~"baz") }
}
}
}
fn main() {
let mut t1 = Tokenizer { state: InATag(~"foo") };
match t1 {
Tokenizer { state: InATag(*) } => { t1.state = Outside }
Tokenizer { state: Outside } => { /* impossible */ }
}
io::println(fmt!("Hello t1: %?", t1));
let mut t2 = Tokenizer { state: InATag(~"bar") };
t2.toggle();
io::println(fmt!("World t2: %?", t2));
}
I admit, I actually did not expect it to be as easy as the above, and I can easily believe that small changes to the above code could cause it to start failing to borrow-check. But without a more fleshed out example from the asker, it is hard to tell whether the above code would suit his purposes or not.
Oh, here's the output when I compile and run the code:
% rustc /tmp/m.rs
warning: no debug symbols in executable (-arch x86_64)
% /tmp/m
Hello t1: {state: Outside}
World t2: {state: Outside}
%

Resources