How to change the outside variable in inner function

I'm writing a state machine in Rust.

trait AtoI {
    fn process(&self, c: char, a: i32) -> i32;
}

fn main() {
    let mut state: Box<dyn AtoI> = Box::new(AtoISign);

    struct AtoISign;
    impl AtoI for AtoISign {
        fn process(&self, c: char, a: i32) -> i32 {
            state = Box::new(AtoIPos);
            return 0;
        }
    }

    struct AtoIPos;
    impl AtoI for AtoIPos {
        fn process(&self, c: char, a: i32) -> i32 {
            return 0;
        }
    }

    state.process('0', 0);
}

A state is a struct which implements trait AtoI. In some conditions, I need to change the value of the ourside variable state in function process. But rust compiler complains:

error[E0434]: can't capture dynamic environment in a fn item
  --> src/main.rs:11:13
   |
11 |             state = Box::new(AtoIPos);
   |             ^^^^^
   |
   = help: use the `|| { ... }` closure form instead

Question: How could I change the state variable in the process function?

Something I tried:

  1. Pass a closure parameter to function process. It complains: "AtoI can't be dyn since it has a function of generic parameter".
  2. Write a closure inside function process and capture state. It fails because closure can only capture the variables of the same scope.
  3. Pass state in the parameter of function process. It complains: "use mutable borrowed variable with immutable borrowed variable".
1 Like
trait AtoI {
    fn process_inner(&self, c: char, a: i32) -> (Option<Box<dyn AtoI>>, i32);
}

impl dyn AtoI {
    fn process(self : &mut Box<Self>, c: char, a: i32) -> i32 {
        let (new, out) = self.process_inner(c,a);
        if let Some(new) = new {
            *self = new
        }
        out
    }
}

fn main() {
    let mut state: Box<dyn AtoI> = Box::new(AtoISign);

    struct AtoISign;
    impl AtoI for AtoISign {
        fn process_inner(&self, c: char, a: i32) -> (Option<Box<dyn AtoI>>, i32) {
            return (Some(Box::new(AtoIPos)), 0);
        }
    }

    struct AtoIPos;
    impl AtoI for AtoIPos {
        fn process_inner(&self, c: char, a: i32) -> (Option<Box<dyn AtoI>>, i32) {
            return (None, 0);
        }
    }

    state.process('0', 0);
}

here is a solution i could think of.

trying to refere to an outer variable inside an impl of a method for a type is a truly horrifying idea that can't work for way too many reasons to consider listing them.

trying to smuggle state inside through an argument is also impossible, if slighlty less ridiculous, because self is state, so that would mean havig a &mut to state with a & to state at the same time which is nonsense

what you want is for state to change when you call process on it. that is very simple.
so process must take &mut self, and modify self, because self is state.
now there is a slight problem of course. if you try to use the process method from AtoI, then you won't be able to know that you're inside a Box<dyn>, so you won't be able to reassign.
that is why i had to use a trick, by putting process on dyn Atoi, and requiring &mut Box<Self>, that way you can do the reassignement.

Thank you very much for the quick answering.

I'm quite new to Rust. So I don't fully understand this:

trying to refere to an outer variable inside an impl of a method for a type is a truly horrifying idea that can't work for way too many reasons to consider listing them.

And I didn't (yet) read this from the book:

impl dyn AtoI {

So I need some time to digest your idea and verify the solution.

By the way, how to implement a state machine without the tricky things which meets the following requirements?

  1. Use trait object. Elegant and effective, follow open close principle. No branches, no enums.
  2. Efficient state change. Don't do assignment when the state does NOT change.
    impl AtoI for AtoISign {
        fn process(&self, c: char, a: i32) -> i32 {
            state = Box::new(AtoIPos);
            return 0;
        }
    }

this is an implementation of a trait for a type. it adds a method on every instance of that type everywhere forever.

state is just some random local variable. there is no way to know if it will even exist at all 2 lines of code later, even less where it will be or wether it will be in a state where it can be modified. so trying to somehow refer to it on every call to AtoISign::process that ever will happen makes no sense.

if you want global values you should look at statics or consts

ultimately, i am quite convinced that to do what you want you will need some variant of what i have done. if you don't want to make the method on dyn AtoI directly, you can make a struct that contains a Box<dyn Atoi> instead, but ultimately i believe you will need the process_inner that returns an optional new state

If I do the following:

for c in s.chars() {
  state.process(c, 0);
}

And according to this line:

if let Some(new) = new {
    *self = new
}

There would be redundant assignments since it assigns if new is not NONE. Could we only do assignment when necessary?

And,

this is an implementation of a trait for a type. it adds a method on every instance of that type everywhere forever.

Excellent point of view. You are right. I don't need a type. What I need is 3 functions (or function pointers).

Here is the original implementation:

pub fn my_atoi(s: String) -> i32 {
    struct Processor(fn (c: char, a: i32) -> (Processor, i32));

    fn processor_sign(c: char, a: i32) -> (Processor, i32) {
        match c {
            ' ' => (Processor(processor_sign), a),
            '+' => (Processor(processor_digit_positive), a),
            '-' => (Processor(processor_digit_negative), a),
            '0' .. '9' => processor_digit_positive(c, a),
            _ => (Processor(processor_end), a)
        }
    };

    fn processor_digit_positive(c: char, a: i32) -> (Processor, i32) {
        match c {
            '0' .. '9' => (Processor(processor_digit_positive), a.saturating_mul(10).saturating_add(c.to_digit(10).unwrap() as i32)),
            _ => (Processor(processor_end), a)
        }
    };

    fn processor_digit_negative(c: char, a: i32) -> (Processor, i32) {
        match c {
            '0' .. '9' => (Processor(processor_digit_negative), a.saturating_mul(10).saturating_sub(c.to_digit(10).unwrap() as i32)),
            _ => (Processor(processor_end), a)
        }
    };

    fn processor_end(c:char, a:i32) -> (Processor, i32) {
        panic!();
    }
    
    let (mut p, mut r) = (Processor(processor_sign), 0);
    for c in s.chars() {
        (p, r) = p.0(c, r);
        if p.0 == processor_end {
            return r;
        }
    }
    return r;
}

Notice this line: (p, r) = p.0(c, r);. This line will do assignment when processing every character. Is it possible to do assignment when necessary?

no there wouldn't, what are you talking about ? there would be one assignement on the first call to go from AtoISign to AtoIPos then it's done

no there wouldn't, what are you talking about ? there would be one assignement on the first call to go from AtoISign to AtoIPos then it's done

Sorry, I didn't say it clearly. I edit my reply and could you please see it again?

to fix this use case you can use the fact that function pointers are Copy, like so :

pub fn my_atoi(s: &str) -> i32 {
    struct Processor(fn(&mut Processor, c: char, a: i32) -> i32);
    
    fn processor_sign(out : &mut Processor, c: char, a: i32) -> i32 {
        match c {
            ' ' => a,
            '+' => {*out = Processor(processor_digit_positive); a},
            '-' => {*out = Processor(processor_digit_negative); a},
            '0' .. '9' => {*out = Processor(processor_digit_positive); processor_digit_positive(out, c,0)},
            _ => {*out = Processor(processor_end); a}
        };
        a
    };

    fn processor_digit_positive(out : &mut Processor, c: char, a: i32) -> i32 {
        match c {
            '0' .. '9' => a.saturating_mul(10).saturating_add(c.to_digit(10).unwrap() as i32),
            _ => {*out = Processor(processor_end); a}
        }
    };

    fn processor_digit_negative(out : &mut Processor, c: char, a: i32) -> i32 {
        match c {
            '0' .. '9' => a.saturating_mul(10).saturating_sub(c.to_digit(10).unwrap() as i32),
            _ => {*out = Processor(processor_end); a}
        }
    };

    fn processor_end(out : &mut Processor, c:char, a:i32) -> i32 {
        panic!();
    }
    
    let (mut p, mut r) = (Processor(processor_sign), 0);
    for c in s.chars() {
        r = p.0(&mut p,c, r);
        if p.0 == processor_end {
            return r;
        }
    }
    return r;
}

but it would be much better to not have processor_end at all, as well as to pass the result by mutable reference.

pub fn my_atoi(s: &str) -> i32 {
    struct Processor(Option<fn(&mut Processor, char, &mut i32)>);
    
    fn processor_sign(out : &mut Processor, c: char, a: &mut i32) {
        match c {
            ' ' => {},
            '+' => *out = Processor(Some(processor_digit_positive)),
            '-' => *out = Processor(Some(processor_digit_negative)),
            '0' .. '9' => {*out = Processor(Some(processor_digit_positive)); processor_digit_positive(out, c,a)},
            _ => *out = Processor(None)
        }
    };

    fn processor_digit_positive(out : &mut Processor, c: char, a: &mut i32) {
        match c {
            '0' .. '9' => *a = a.saturating_mul(10).saturating_add(c.to_digit(10).unwrap() as i32),
            _ => *out = Processor(None)
        }
    };

    fn processor_digit_negative(out : &mut Processor, c: char, a: &mut i32) {
        match c {
            '0' .. '9' => *a = a.saturating_mul(10).saturating_sub(c.to_digit(10).unwrap() as i32),
            _ => *out = Processor(None)
        }
    };


    let (mut p, mut r) = (Processor(Some(processor_sign)), 0);
    for c in s.chars() {
        if let Some(process) = p.0 {
            process(&mut p, c, &mut r)
        } else {
            return r
        }
    }
    return r;
}

Fascinating! Thanks.
It took me several days to find out a solution. I think Rust is a very good programming language although it's a little bit hard to use it in competitive programming (for state machine, multiple mutable pointers and recursive types such as linked list, binary tree, etc).
I almost finish the investigation of Rust and decide to use it in our production environment.
Thank you again and wish Rust a great future.