1
votes

I've been playing with rust and I was doing a very silly program which combined traits, structs, some silly concurrency and generics. Everything was quite understandable for me until I faced some problems when sending a trait between threads.

Firs of all, at one point a realized I needed a vector of Boxed Animals in order to store the different elements that comply with the Animal trait, ok, I get that because the trait is an abstraction of some other particular structs which can vary in "size" etc, so we have to store them in the heap. But then the first weird point for me was that because I had to use a Box to store the traits, then I had to implement my trait for the Boxed trait also (see (*1) on the code comments).

Once I did that, the program was correct for the compiler, but I ran into some problems at runtime that I don't understand. The error that I'm getting is:

thread '<unknown>' has overflowed its stack
fatal runtime error: stack overflow
[1]    4175 abort (core dumped)  cargo run

The code is:

use std::thread;
use std::sync::mpsc::{Sender};
use std::sync::mpsc;
use std::time;

trait Animal {
    fn poop(&self) -> Poop;
}

#[derive(Debug)]
enum Weight {
    VeryLight,
    Light,
    Medium,
    Heavy,
    SuperHeavy,
}

#[derive(Debug)]
struct Poop {
    shape: String,
    weight: Weight,
}

struct Wombat;

impl Animal for Wombat {
    fn poop(&self) -> Poop {
        Poop {
            shape: "cubic".to_string(),
            weight: Weight::Light,
        }
    }
}

struct Cat;

impl Animal for Cat {
    fn poop(&self) -> Poop {
        Poop {
            shape: "cylindrical".to_string(),
            weight: Weight::VeryLight,
        }
    }
}

// (*1) This seemed weird for me and I'm not sure the 
// impl es correct
impl Animal for Box<dyn Animal + Send> {
    fn poop(&self) -> Poop {
        let t: &dyn Animal = self;
        // self.poop()
        t.poop()

    }
}

fn feed_animal<T> (a: T, tx: Sender<String>)
    where T: Animal + Send + 'static {
    
    thread::spawn(move || {
        thread::sleep(time::Duration::from_secs(2));
        tx.send(format!("{:?}", a.poop()))
    });
}

fn main() {
    let mut animals: Vec<Box<dyn Animal + Send>> = Vec::new();
    animals.push(Box::new(Wombat));
    animals.push(Box::new(Cat));
    
    let (tx, rx) = mpsc::channel();

    for a in animals {
        let txi = tx.clone();
        feed_animal(a, txi);
    }

    drop(tx);

    for r in rx {
        println!("The animal just pooped: {:?}", r);
    }
}

I'm a little lost with the error message honestly. Usually when I see this kind of error in some other programing languages is due to an infinite loop that will overflow the stack, but in this case I guess there must be some error in the way I "send" the Boxed trait to the child thread that makes rust handle the child thread stack memory badly at runtime.. I'm not sure.

Any hint to the right direction will be very welcomed. Thank you.

2

2 Answers

2
votes

I have changed your code a little bit. You can check it using this playground link.

I think you made one mistake in this function signature,

fn feed_animal<T> (a: T, tx: Sender<String>)
    where T: Animal + Send + 'static {
}

where a should be Box<T>, therefore you wouldn't need to implement Animal for Box<dyn Animal + Send>.

Code after some editing is shown below.

use std::thread;
use std::sync::mpsc::{Sender};
use std::sync::mpsc;
use std::time;

trait Animal {
    fn poop(&self) -> Poop;
}

#[derive(Debug)]
enum Weight {
    VeryLight,
    Light,
    Medium,
    Heavy,
    SuperHeavy,
}

#[derive(Debug)]
struct Poop {
    shape: String,
    weight: Weight,
}

struct Wombat;

impl Animal for Wombat {
    fn poop(&self) -> Poop {
        Poop {
            shape: "cubic".to_string(),
            weight: Weight::Light,
        }
    }
}

struct Cat;

impl Animal for Cat {
    fn poop(&self) -> Poop {
        Poop {
            shape: "cylindrical".to_string(),
            weight: Weight::VeryLight,
        }
    }
}

fn feed_animal<T: ?Sized> (a: Box<T>, tx: Sender<String>)
    where T: Animal + Send + 'static {
    
    thread::spawn(move || {
        thread::sleep(time::Duration::from_secs(2));
        tx.send(format!("{:?}", a.poop()))
    });
}

fn main() {
    let mut animals: Vec<Box<dyn Animal + Send>> = Vec::new();
    animals.push(Box::new(Wombat{}));
    animals.push(Box::new(Cat{}));
    
    let (tx, rx) = mpsc::channel();

    for a in animals {
        let txi = tx.clone();
        feed_animal(a, txi);
    }

    for r in rx {
        println!("The animal just pooped: {:?}", r);
    }
}
2
votes

Let's focus on this code:

impl Animal for Box<dyn Animal + Send> {
    fn poop(&self) -> Poop {
        let t: &dyn Animal = self;
        // self.poop()
        t.poop()

    }
}

Here, we get self of type &Box<dyn Animal + Send>, cast it to &dyn Animal, and make it poop.

I believe you were confused by the levels of indirection, and the presence of an additional & in particular. By casting Box<dyn Animal + Send> to dyn Animal, you get a dynamicised version of the Box. Later on, you call poop on that dynamicised version, which makes the function unintentionally recursive.

The correct code is

let t: &dyn Animal = &**self;

By the way, there are tools to get a stack trace in case of problems. One such solution, a multi-purpose one, is to run valgrind target/debug/your-binary.