Can't move behind a shared reference

The following:

enum Action<T>
where
    T: FnMut(State) -> State,
{
    Help(T),
    Hunt,
    Quit(T),
    Rest(Duration),
    Status(T),
    Travel(Duration, u64),
}

enum SimpleAction {
    Hunt,
    Travel(Duration, u64),
    Rest(Duration),
}

impl From<SimpleAction> for Action<Box<dyn FnMut(State) -> State>> {
    fn from(action: SimpleAction) -> Self {
        use SimpleAction::*;

        match action {
            Hunt => Action::<Box<dyn FnMut(State) -> State>>::Hunt,
            Travel(d, i) => Action::<Box<dyn FnMut(State) -> State>>::Travel(d, i),
            Rest(d) => Action::<Box<dyn FnMut(State) -> State>>::Rest(d),
        }
    }
}

/// The main function that uses an Action to get a new State
///
/// # Examples
/// ```
/// root_reducer(
///   State {
///     date: Utc.ymd(2020, 3, 1),
///     miles: 2000,
///     food: 500,
///     health: 5,
///     hunt_days: 2,
///     rest_days: 2,
///     travel_days: 3,
///     travel_distance: 30,
///   },
///   Action::Travel
/// )
/// ```
fn root_reducer<T>(state: &State, action: &Action<T>) -> State
where
    T: FnMut(State) -> State,
{
    match action {
        Action::Travel(days, distance) => State {
            date: state.date + *days,
            miles: state.miles - distance,
            ..*state
        },
        Action::Rest(days) => State {
            date: state.date + *days,
            // No more than 5 health
            health: if state.health < 5 {
                state.health + 1
            } else {
                state.health
            },
            ..*state
        },
        Action::Hunt => State {
            date: state.date + Duration::days(state.hunt_days),
            food: state.food + 100,
            ..*state
        },
        Action::Status(mut status_function) => status_function(*state),
        Action::Help(mut help_function) => help_function(*state),
        Action::Quit(mut quit_mock) => quit_mock(*state),
    }
}

Outputs the error

cannot move out of `action.0` which is behind a shared reference

note: move occurs because these variables have types that don't implement the `Copy` trait

I can provide full code on request.

FnMut(State) -> State consumes its argument (takes it by value and doesn't give it back). But root_reducer only has a borrowed reference (&State), which means the State is still owned by someone else and root_reducer is not allowed to consume it.

Depending on your needs, one solution is to make root_reducer also take its argument by value. Another is to add a Clone bound to T, so you can create a new State when you need to pass it by value, like: help_function(state.clone()).

1 Like

Can I see an example?

The first one would look something like:

fn root_reducer<T>(state: State, action: &mut Action<T>) -> State

and the caller would need to be modified appropriately.

The second option could be done by implementing the Clone trait for the State type, like:

#[derive(Clone)]
struct State { ... }

and then calling .clone() whenever you need a new copy of the State.

1 Like

Then, in my fn main:

use redux_rs::{Store, Subscription};

fn main() {
    let inital_state = State {
        date: Utc.ymd(2020, 3, 1),
        miles: 2000,
        food: 500,
        health: 5,
        hunt_days: 2,
    };
    let mut store = Store::new(root_reducer, inital_state);
}

I'm getting:

mismatched types

types differ in mutability

note: expected fn pointer `for<'r, 's> fn(&'r _, &'s _) -> _`
            found fn item `for<'r, 's> fn(&'r State, &'s mut Action<_>) -> State {root_reducer::<_>}`

Ah. So, the redux_rs library requires your functions to follow a certain interface. In that case, you may need to switch from FnMut to Fn, so that you can call your functions through a shared reference.

(If they need to mutate any captured variables, those would need to use "interior mutable" types like Cell or Mutex.)

1 Like

I'm getting a bunch of errors. That Fn type has to also accept a closure. Any ideas?

Need more details.

#![allow(dead_code)]

use chrono::prelude::*;
use chrono::Duration;
use redux_rs::{Store, Subscription};
use std::boxed::Box;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct State {
    date: Date<Utc>,
    miles: u64,
    food: u64,
    health: u64,
    hunt_days: i64,
}

enum Action<T>
where
    T: FnMut(State) -> State,
{
    Help(T),
    Hunt,
    Quit(T),
    Rest(Duration),
    Status(T),
    Travel(Duration, u64),
}

enum SimpleAction {
    Hunt,
    Travel(Duration, u64),
    Rest(Duration),
}

impl From<SimpleAction> for Action<Box<dyn Fn(State) -> State>> {
    fn from(action: SimpleAction) -> Self {
        use SimpleAction::*;

        match action {
            Hunt => Action::<Box<dyn Fn(State) -> State>>::Hunt,
            Travel(d, i) => Action::<Box<dyn Fn(State) -> State>>::Travel(d, i),
            Rest(d) => Action::<Box<dyn Fn(State) -> State>>::Rest(d),
        }
    }
}

/// The main function that uses an Action to get a new State
///
/// # Examples
/// ```
/// root_reducer(
///   State {
///     date: Utc.ymd(2020, 3, 1),
///     miles: 2000,
///     food: 500,
///     health: 5,
///     hunt_days: 2,
///     rest_days: 2,
///     travel_days: 3,
///     travel_distance: 30,
///   },
///   Action::Travel
/// )
/// ```
fn root_reducer<T>(state: &State, action: &mut Action<T>) -> State
where
    T: Fn(State) -> State,
{
    match action {
        // Travel: Move the player forward by distance and move the date forward by days
        Action::Travel(days, distance) => State {
            date: state.date + *days,
            miles: state.miles - *distance,
            ..*state
        },

        // Rest: Regenerate one health (up to 5) by stopping for rest_days
        Action::Rest(days) => State {
            date: state.date + *days,
            // No more than 5 health
            health: if state.health < 5 {
                state.health + 1
            } else {
                state.health
            },
            ..*state
        },

        // Hunt: Add one hundred pounds of food by stopping for hunt_days
        Action::Hunt => State {
            date: state.date + Duration::days(state.hunt_days),
            food: state.food + 100,
            ..*state
        },

        // Print the status of the game
        Action::Status(mut status_function) => status_function(*state),

        // Print commands and what they do
        Action::Help(mut help_function) => help_function(*state),

        // End the game
        Action::Quit(mut quit_mock) => quit_mock(*state),
    }
}

fn main() {
    let inital_state = State {
        date: Utc.ymd(2020, 3, 1),
        miles: 2000,
        food: 500,
        health: 5,
        hunt_days: 2,
    };
    let mut store = Store::new(root_reducer, inital_state);
}

#[cfg(test)]

mod tests {
    use super::*;

    #[test]
    fn test_travel() {
        let initial_state = State {
            date: Utc.ymd(2020, 3, 1),
            miles: 2000,
            food: 500,
            health: 5,
            hunt_days: 2,
        };

        let result_state = State {
            miles: 1970,
            date: Utc.ymd(2020, 3, 4),
            ..initial_state
        };
        let result_state_with_more_days: State = State {
            date: Utc.ymd(2020, 3, 5),
            ..result_state
        };
        let result_state_with_more_miles: State = State {
            miles: 1960,
            ..result_state
        };

        let duration = Duration::days(3);
        let longer_duration = Duration::days(4);
        let distance = 30;
        let longer_distance = 40;

        let default_action = SimpleAction::Travel(duration, distance).into();

        assert_eq!(root_reducer(&initial_state, default_action), result_state);

        assert_eq!(
            root_reducer(
                &initial_state,
                SimpleAction::Travel(longer_duration, distance).into()
            ),
            result_state_with_more_days
        );
        assert_eq!(
            root_reducer(
                &initial_state,
                SimpleAction::Travel(duration, longer_distance).into()
            ),
            result_state_with_more_miles
        );
    }

    #[test]
    fn test_rest() {
        let initial_state = State {
            date: Utc.ymd(2020, 3, 1),
            miles: 2000,
            food: 500,
            health: 4,
            hunt_days: 2,
        };
        let duration = Duration::days(2);

        assert_eq!(
            root_reducer(&initial_state, SimpleAction::Rest(duration).into()),
            State {
                date: Utc.ymd(2020, 3, 3),
                ..initial_state
            }
        );
        assert_eq!(
            root_reducer(&initial_state, SimpleAction::Rest(duration).into()),
            State {
                date: Utc.ymd(2020, 3, 3),
                ..initial_state
            }
        );
    }
    #[test]
    fn test_hunt() {
        let initial_state = State {
            date: Utc.ymd(2020, 3, 1),
            miles: 2000,
            food: 500,
            health: 5,
            hunt_days: 2,
        };
        let state_with_more_days: State = State {
            hunt_days: 3,
            ..initial_state
        };
        let result_state: State = State {
            date: Utc.ymd(2020, 3, 3),
            food: 600,
            ..initial_state
        };
        let result_state_with_more_days = State {
            date: Utc.ymd(2020, 3, 4),
            hunt_days: 3,
            ..result_state
        };

        assert_eq!(
            root_reducer(&initial_state, SimpleAction::Hunt.into()),
            result_state
        );

        assert_eq!(
            root_reducer(&state_with_more_days, SimpleAction::Hunt.into()),
            result_state_with_more_days
        );
    }

    #[test]
    fn test_status() {
        let default_state = State {
            date: Utc.ymd(2020, 3, 1),
            miles: 1970,
            food: 500,
            health: 5,
            hunt_days: 2,
        };
        let mut status_mock_called = false;

        let status_mock = |state: State| -> State {
            status_mock_called = true;
            return state;
        };
        assert_eq!(
            root_reducer(&default_state, &mut Action::Status(status_mock)),
            default_state
        );

        assert!(status_mock_called);
    }

    #[test]
    fn test_help() {
        let mut help_mock_called = false;

        let mut help_mock = |state: State| -> State {
            help_mock_called = true;
            return state;
        };
        let default_state = State {
            date: Utc.ymd(2020, 3, 1),
            miles: 1970,
            food: 500,
            health: 5,
            hunt_days: 2,
        };
        root_reducer(&default_state, &mut Action::Help(help_mock));
        assert!(help_mock_called);
    }
    fn test_quit() {
        let mut quit_mock_called = false;

        let quit_mock = |state: State| -> State {
            quit_mock_called = true;
            return state;
        };
        let default_state = State {
            date: Utc.ymd(2020, 3, 1),
            miles: 1970,
            food: 500,
            health: 5,
            hunt_days: 2,
        };
        root_reducer(&default_state, &mut Action::Quit(quit_mock));
        assert!(quit_mock_called);
    }
}

Outputs the error on line 115:

mismatched types

types differ in mutability

note: expected fn pointer `for<'r, 's> fn(&'r _, &'s _) -> _`
            found fn item `for<'r, 's> fn(&'r State, &'s mut Action<_>) -> State {root_reducer::<_>}`

And on all the tests where I need to pass in a closure to one of the fat enums:

expected a closure that implements the `Fn` trait, but this closure only implements `FnMut`

redux_rs does not give reduces exclusive (&mut T) access to the actions, so they won’t be able to call FnMut closures. You’ll need to change the closure to implement the Fn trait, which means it must work with only shared (&T) access to its environment. You can do this by using shared mutable types like Cell and RefCell for anything that gets mutated inside a reducer.

Or, move such data into the State type and, rather than mutate it, return a new State with the new value.

Can I see an example? Sorry, I'm just really new to rust and in a bit over my head.

These are the changes I had to make to get that example to compile:

diff --git a/src/main.rs b/src/main.rs
index 048d5b1..2bd462f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -14,15 +14,12 @@ struct State {
     hunt_days: i64,
 }
 
-enum Action<T>
-where
-    T: FnMut(State) -> State,
-{
-    Help(T),
+enum Action {
+    Help(Box<dyn Fn(State) -> State>),
     Hunt,
-    Quit(T),
+    Quit(Box<dyn Fn(State) -> State>),
     Rest(Duration),
-    Status(T),
+    Status(Box<dyn Fn(State) -> State>),
     Travel(Duration, u64),
 }
 
@@ -32,14 +29,14 @@ enum SimpleAction {
     Rest(Duration),
 }
 
-impl From<SimpleAction> for Action<Box<dyn Fn(State) -> State>> {
+impl From<SimpleAction> for Action {
     fn from(action: SimpleAction) -> Self {
         use SimpleAction::*;
 
         match action {
-            Hunt => Action::<Box<dyn Fn(State) -> State>>::Hunt,
-            Travel(d, i) => Action::<Box<dyn Fn(State) -> State>>::Travel(d, i),
-            Rest(d) => Action::<Box<dyn Fn(State) -> State>>::Rest(d),
+            Hunt => Action::Hunt,
+            Travel(d, i) => Action::Travel(d, i),
+            Rest(d) => Action::Rest(d),
         }
     }
 }
@@ -62,9 +59,7 @@ impl From<SimpleAction> for Action<Box<dyn Fn(State) -> State>> {
 ///   Action::Travel
 /// )
 /// ```
-fn root_reducer<T>(state: &State, action: &mut Action<T>) -> State
-where
-    T: Fn(State) -> State,
+fn root_reducer(state: &State, action: &Action) -> State
 {
     match action {
         // Travel: Move the player forward by distance and move the date forward by days
@@ -94,13 +89,13 @@ where
         },
 
         // Print the status of the game
-        Action::Status(mut status_function) => status_function(*state),
+        Action::Status(status_function) => status_function(*state),
 
         // Print commands and what they do
-        Action::Help(mut help_function) => help_function(*state),
+        Action::Help(help_function) => help_function(*state),
 
         // End the game
-        Action::Quit(mut quit_mock) => quit_mock(*state),
+        Action::Quit(quit_mock) => quit_mock(*state),
     }
 }
 
1 Like

Why did you delete that last one?

I realized I hadn't built the tests yet. The tests require changes like this:

     fn test_quit() {
-        let mut quit_mock_called = false;
+        use std::cell::Cell;
+        let mut quit_mock_called = Cell::new(false);
 
         let quit_mock = |state: State| -> State {
-            quit_mock_called = true;
+            quit_mock_called.set(true);
             return state;
         };
         let default_state = State {
@@ -285,7 +281,7 @@ mod tests {
             health: 5,
             hunt_days: 2,
         };
-        root_reducer(&default_state, &mut Action::Quit(quit_mock));
-        assert!(quit_mock_called);
+        root_reducer(&default_state, &Action::Quit(Box::new(quit_mock)));
+        assert!(quit_mock_called.get());
     }
1 Like

I'm still getting that error

type annotations needed for `redux_rs::store::Store<State, Action<T>>`

cannot infer type for type parameter `T` declared on the function `root_reducer`

Hmm, I see. Here's a complete file with some more changes that I needed to get the tests to run:

The main additional change was adding a lifetime parameter to Action so that it is allowed to capture references to local variables, like quit_mock_called.

You'll notice that I also removed the type parameter from Action, and instead made it always use Box<dyn Fn> for its callbacks. I think you'll find that this simplifies things quite a bit without removing any important functionality. But if you end up needing to make it generic again, it is possible, but will require adding a type annotations in a lot more places.

3 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.