How to write the correct fmt::Debug implementation for dyn Trait

So, I'm trying to implement dfsm.

here's the code
enum Language {
	A, B
}

#[derive(Debug)]
pub struct Q0;
#[derive(Debug)]
pub struct Q1;
#[derive(Debug)]
pub struct Q2;

trait State {
	fn next(&self, lang: Language) -> Box<dyn State>;
}

impl std::fmt::Debug for dyn State {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl State for Q2 {
	fn next(&self, lang: Language) -> Box<dyn State> {
		match lang {
			Language::A => Box::new(Q0),
			Language::B => Box::new(Q1),
		}
	}
}
impl State for Q0 {
	fn next(&self, lang: Language) -> Box<dyn State> {
		match lang {
			Language::A => Box::new(Q0),
			Language::B => Box::new(Q1),
		}
	}
}
impl State for Q1 {
	fn next(&self, lang: Language) -> Box<dyn State> {
		match lang {
			Language::A => Box::new(Q0),
			Language::B => Box::new(Q1),
		}
	}
}

#[derive(Debug)]
struct CurrentState{inner: Box<dyn State> }


impl CurrentState {
	pub fn new() -> Self {
		Self { inner: Box::new(Q0) }
	}

	pub fn transition(&mut self, lang: Language) {
		self.inner  = self.inner.next(lang);
	}
}

fn main() {
    let mut state = CurrentState::new(); // Q2
    state.transition(Language::A);
    dbg!(&state);
	state.transition(Language::B);
    dbg!(&state);
	state.transition(Language::A);
    dbg!(&state);
}

But if I run the program. It gives this output

[src/main.rs:63] &state = CurrentState {
    inner: 
thread 'main' has overflowed its stack
fatal runtime error: stack overflow
Aborted

I've no idea how to correctly implement Debug trait for State. Basically what I want is the name of struct implementing the state trait to printed in the output.
Also, you can see that I'm using Box to store the State(Box<dyn State>). Is there any other way such that I don't have to use the Box for the dyn State. So, I don't have any overhead of allocations

You can't do it this way because when you implement something for dyn State, it's already a type erased object and you don't have access to implementations specific to that type. You can only access the methods of the trait.

Instead, make Debug a supertrait of your trait:

trait State: std::fmt::Debug {...}

Then dyn State will implement fmt::Debug automatically, so you don't need to implement it.

Playground

3 Likes

Thanks, it's now working correctly.
Any idea how to you to use dyn State without Box?

It depends on how you want to work with it. If we're talking about variable types, dyn State must always be behind some kind of pointer. Besides Box, it can be, for example, Rc, Arc, SmallBox, or a plain reference (&dyn State, &mut dyn State).

hmm, I think for my usecase, SmallBox might be the correct tool. Ultimately, what I want is to get to the performance level of this code

After going through the SmallBox docs, I felt very complicated to deal with it. Anyway, I tried by just using the references, it worked perfectly. Here's my final code:

enum Language {
	A, B
}

#[derive(Debug)]
pub struct Q0;
const S0: &dyn State = &Q0 as &dyn State;
#[derive(Debug)]
pub struct Q1;
const S1: &dyn State = &Q1 as &dyn State;
#[derive(Debug)]
pub struct Q2;
const S2: &dyn State = &Q2 as &dyn State;

trait State: std::fmt::Debug {
	fn next(&self, lang: Language) -> &dyn State;
}


impl State for Q2 {
	fn next(&self, lang: Language) -> &dyn State {
		match lang {
			Language::A => S0,
			Language::B => S1,
		}
	}
}
impl State for Q0 {
	fn next(&self, lang: Language) -> &dyn State {
		match lang {
			Language::A => S1,
			Language::B => S2,
		}
	}
}
impl State for Q1 {
	fn next(&self, lang: Language) -> &dyn State {
		match lang {
			Language::A => S2,
			Language::B => S0,
		}
	}
}

#[derive(Debug)]
struct CurrentState{inner: &'static dyn State }


impl CurrentState {
	pub fn new() -> Self {
		Self { inner: S0 }
	}

	pub fn transition(&mut self, lang: Language) {
		self.inner  = self.inner.next(lang);
	}
}

fn main() {
    let mut state = CurrentState::new(); // Q2
    state.transition(Language::A);
    dbg!(&state);
	state.transition(Language::B);
    dbg!(&state);
	state.transition(Language::A);
    dbg!(&state);
}

I really hope that the compiled code is equivalent to this code

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.