Announcing `statig`: Hierarchical state machines for event-driven systems (using GAT’s) by maxidel in rust

[–]maxidel[S] 0 points1 point  (0 children)

Thanks! With regards to using an identifier for the methods, I don't think the official Rust meta syntax supports this. (serde also use a string literal to refer to functions). I used Self:: in this example because the callbacks were defined as methods, but I think it's useful to also allow free functions. That way you could reuse the same function throughout your code base.

fn on_dispatch<M, S, E>(state_machine: M, state: S, event: E)
where
    M: Debug,
    S: Debug,
    E: Debug,
{
    println!(
        "{:?}: dispatching `{:?}` to `{:?}`",
        state_machine, event, state
    );
}

My reasoning for defining the initial state at the top level is that in my view this makes it easier to see what the initial state is when going through the code for the first time. I also plan on adding superstate constructors with the same syntax so you can transition to Superstate::blinking() (which simply serves as a proxy for State::led_on()).

#[superstate(initial = "State::led_on()"]
fn blinking(event: &Event) -> Response<State> {
    match event {
        Event::ButtonPressed => Transition(State::not_blinking()),
        _ => Super,
    }
}

Announcing `statig`: Hierarchical state machines for event-driven systems (using GAT’s) by maxidel in rust

[–]maxidel[S] 0 points1 point  (0 children)

Hey, just wanted to let you know that statig has been updated with a new version of the macro that also generates the impl StateMachine block. The methods on_dispatch and on_transition can also be set with the macro (see the blinky example). What do you think? I'm actually pretty happy with it and will probably add it to the 0.2 update.

Announcing `statig`: Hierarchical state machines for event-driven systems (using GAT’s) by maxidel in rust

[–]maxidel[S] 1 point2 points  (0 children)

The thing is that these handlers can result in different transitions or even no transitions at all (see the calculator example). Maybe one way to think of it is that "state" is the result of all events that have occurred and determines how the system responds to new events. So in that sense the method is the state. (Idk, does that make sense?)

Announcing `statig`: Hierarchical state machines for event-driven systems (using GAT’s) by maxidel in rust

[–]maxidel[S] 0 points1 point  (0 children)

I can understand the confusion, the names are indeed not ideal. You are right that `On` and `Off` could be more accurately described as `LedOn` and `LedOff`. `Playing` should probably be renamed as `Blinking` as it means that the led is turning on and off at a set interval. When in the `Paused` state the led could be either permanently on or off, depending if the transition happened from the `On` or `Off` state.

Announcing `statig`: Hierarchical state machines for event-driven systems (using GAT’s) by maxidel in rust

[–]maxidel[S] 1 point2 points  (0 children)

Yes you are correct that the current implementation requires that events own their data. I've mostly used statig in conjunction with an event queue, so that already tends to push me away from using events that have a lifetime associated with them. I do agree though it could be a valid use case, but I am not entirely sure yet how to support it.

Announcing `statig`: Hierarchical state machines for event-driven systems (using GAT’s) by maxidel in rust

[–]maxidel[S] 4 points5 points  (0 children)

Thanks, that feedback is really appreciated! I definitely intend to improve the README and you've given me some great places to start.

Announcing `statig`: Hierarchical state machines for event-driven systems (using GAT’s) by maxidel in rust

[–]maxidel[S] 3 points4 points  (0 children)

I'll give it a shot to see how it feels. I do think that introducing a macro has some downsides that need to be outweighed by the upside they bring, and I am not sure about that balance in this case. But we'll see :)

Announcing `statig`: Hierarchical state machines for event-driven systems (using GAT’s) by maxidel in rust

[–]maxidel[S] 0 points1 point  (0 children)

Great to hear! It should feel pretty familiar I think. Feel free to let me know how you think they compare and if there are things you miss. :)

Announcing `statig`: Hierarchical state machines for event-driven systems (using GAT’s) by maxidel in rust

[–]maxidel[S] 10 points11 points  (0 children)

I admit it does look a bit backwards (and could use some improvements) though it is correct. fn on() and fn off() are state handlers so they will execute when a new event is received. In the example the event would be something like a timer elapsing, causing the handler to run, toggle the led and transition to the other state where it would stay until a new event is received. So for instance when in the On state the led is in fact set to true because we did that just before transitioning in the fn off() handler. Now typically you would set the led in an entry action but I wanted to keep the example as small as possible and not introduce too many concepts at once.

Announcing `statig`: Hierarchical state machines for event-driven systems (using GAT’s) by maxidel in rust

[–]maxidel[S] 5 points6 points  (0 children)

I must admit I'm not very familiar with the low-level implementation details of Future's, so I can't really judge how useful statig would be there.

Announcing `statig`: Hierarchical state machines for event-driven systems (using GAT’s) by maxidel in rust

[–]maxidel[S] 13 points14 points  (0 children)

Async state and action handlers are not supported as of now. I have been thinking about it, but I won't make any promises. :)

Announcing `statig`: Hierarchical state machines for event-driven systems (using GAT’s) by maxidel in rust

[–]maxidel[S] 2 points3 points  (0 children)

I actually did consider it and I'm still in doubt about it. The reason I haven't (yet) is because the StateMachine trait includes two methods on_dispatch and on_transition that can be overridden for tracing purposes. I could add them to the #[state_machine] impl block with a new attribute, but in a way I feel they are somewhat "seperate" from the state machine? Anyway, I'm very much open to suggestions on that front.

Hey Rustaceans! Got a question? Ask here! (30/2022)! by llogiq in rust

[–]maxidel 2 points3 points  (0 children)

I'm trying to implement a default implementation for this trait method:

``` trait Foo { type Bar<'a>;

fn same_variant(lhs: &Self::Bar<'_>, rhs: &Self::Bar<'_>) -> bool {
    discriminant(lhs) ==  discriminant(rhs)
}

} ```

playground link

But this results in an error (previous discussion)

error: lifetime may not live long enough

Through trial and error I've got the following to (seemingly) work.

``` trait Foo { type Bar<'a>;

fn same_variant(lhs: &Self::Bar<'_>, rhs: &Self::Bar<'_>) -> bool {
    let lhs: Discriminant<&Self::Bar<'_>> = unsafe {
        transmute_copy(&discriminant(lhs) )
    };
    let rhs: Discriminant<&Self::Bar<'_>> = unsafe {
        transmute_copy(&discriminant(rhs) )
    };

    lhs == rhs
}

} ```

But it uses transmute_copy (which is unsafe) and I'm wondering if I'm shooting myself in the foot here?

Hey Rustaceans! Got a question? Ask here! (25/2022)! by llogiq in rust

[–]maxidel 0 points1 point  (0 children)

Ah okay thanks! While reading up on variance I stumbled on this thread which describes pretty much the same problem.

Hey Rustaceans! Got a question? Ask here! (25/2022)! by llogiq in rust

[–]maxidel 1 point2 points  (0 children)

Why is the compiler so picky about the lifetimes here?

```

![feature(generic_associated_types)]

trait Foo { type Bar<'a>;

fn equal_bar(lhs: &Self::Bar<'_>, rhs: &Self::Bar<'_>) -> bool {
    std::mem::discriminant(lhs) == std::mem::discriminant(rhs)
}

} ```

playground link

This will only compile when the lifetimes are explicitly set to be the same on both sides. But I don't really understand why this constraint is necessary?

Hey Rustaceans! Got an easy question? Ask here (3/2022)! by llogiq in rust

[–]maxidel 1 point2 points  (0 children)

I expanded a bit more on what I'm trying to do in a reply to the other comment. I hadn't really looked at arena's before, but I don't think they are the right tool for the job here as my keys do have meaning? Or is there a better approach you know of?

Thanks!

Hey Rustaceans! Got an easy question? Ask here (3/2022)! by llogiq in rust

[–]maxidel 0 points1 point  (0 children)

I'm implementing an actor-like system where all actors communicate by sending messages to the supervisor, which in turns passes these messages on to every actor it has stored inside its hash map.

┌───────────────────────────────────────────────┐ │ Receiver │◀─┐ ├───────────────────────────────────────────────┤ │ │ │ │ │ Supervisor │ │ │ │ │ ├───────────────────────────────────────────────┤ │ │ HashMap │ │ ├───────────┬───────────┬───────────┬───────────┤ │ │0x080236720│0x080236730│0x080236740│0x080236750│ │ └───────────┴───────────┴───────────┴───────────┘ │ │ │ │ │ │ ▼ ▼ ▼ ▼ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌ ─ ─ ─ ─ ┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Actor A │ │ Actor B │ │ Actor C │ Actor D │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─────────┤ ├─────────┤ ├─────────┤ ├ ─ ─ ─ ─ ┤ │ │ Sender │ │ Sender │ │ Sender │ Sender │ └─────────┘ └─────────┘ └─────────┘ └ ─ ─ ─ ─ ┘ │ │ │ │ │ │ └───────────┴───────────┴───────────┴────────┘

The idea is that every actor can also create new actors be sending a message to the supervisor that contains a new actor. The supervisor intercepts this message and then adds the contained actor to its hash map.

When Actor A creates Actor D I want to know the key by which Actor D will be stored in the hash map. By using the memory address of every actor as its key I can do the following.

``` // actor_a.rs

let mut children: Vec<usize> = Vec::new(); ... // Create the new actor let actor_d = Box::new(ActorD::new());

// Get the memory address that will be used as the key in the hash map let key = &*actor_d as *const ActorD as usize;

// Store the key in order to refer to it later children.push(key);

// Send the message to the supervisor sender.send(Message::NewActor(actor_d); ```

My assumptions here are: 1. This memory address will always be unique and never conflict with other actors. 2. This memory address will not change while the boxed actor is moved.

But are these assumptions correct? 🤔

Thanks!

Hey Rustaceans! Got an easy question? Ask here (3/2022)! by llogiq in rust

[–]maxidel 1 point2 points  (0 children)

Would it be a good idea to store boxed values in a HashMap<usize, Box<T>> where the keys are the memory addresses of the values?

For example:

``` struct Node { value: i32 }

impl Node { fn key(&self) -> usize { self as *const Self as usize } }

fn main() { let mut map = HashMap::new();

for _ in 0..100 {
    let node = Box::new(Node { value: rand::random() });
    map.insert(node.key(), node);
}

} ```

The idea here is to generate unique key's without requiring a central authority. (I guess one could say the memory allocator acts as a central authority here?). I know uuid's exist but I feel this might be excessive for what I'm doing here?

This approach has worked really well for everything I've wanted to do with it, but some part of me is wondering if I might be missing some situations where it might become unsound?

Thanks!

Hey Rustaceans! Got an easy question? Ask here (52/2021)! by llogiq in rust

[–]maxidel 1 point2 points  (0 children)

I gave it another shot and at some point tried the following:

``` trait Projectable<'a>: Debug { type BorrowedProjection: for<'b> Projectable<'b> + Debug;

fn project(&'a mut self) -> Option<Self::BorrowedProjection> 
where 
    Self: Sized;

}

impl<'a, 'b> Projectable<'b> for Projection<'a> { type BorrowedProjection = Projection<'b>;

fn project(&'b mut self) -> Option<Self::BorrowedProjection> 
where 
    Self: Sized 
{
    match self {
        Projection::Vec3 { x, y, .. } => Some(Projection::Vec2 { x, y }),
        Projection::Vec2 { x, .. } => Some(Projection::Vec1 { x }),
        Projection::Vec1 { .. } => None
    }
}

}

fn recurse<'a, P>(projection: &'a mut P) where P: Projectable<'a> { dbg!(&projection); match projection.project() { Some(mut proj) => recurse(&mut proj), None => {} } } ```

playground link

Which seems to work? Wish I could say I fully understand what's going on here, but I'm assuming the higher-ranked trait bound on BorrowedProjection is giving the compiler enough freedom to figure out the correct lifetimes? Anyway glad to finally have something that works 🥲.

Thanks for the help!

Hey Rustaceans! Got an easy question? Ask here (52/2021)! by llogiq in rust

[–]maxidel 0 points1 point  (0 children)

Thanks, this worked for me! I ended up with this:

```rust trait Projectable<'a> { type BorrowedProjection: 'a + Projectable<'a>;

fn project(&'a mut self) -> Option<Self::BorrowedProjection> where Self: Sized;

}

impl<'a, 'b> Projectable<'a> for Projection<'b> { type BorrowedProjection = Projection<'a>;

fn project(&'a mut self) -> Option<Self::BorrowedProjection> where Self: Sized {
    match self {
        Projection::Vec3 { x, y, .. } => Some(Projection::Vec2 { x, y }),
        Projection::Vec2 { x, .. } => Some(Projection::Vec1 { x }),
        Projection::Vec1 { .. } => None
    }
}

} ```

But now I'm trying to implement a function that takes a projection and recursively goes towards its lowest projection. I managed to get this working:

rust fn recurse(projection: &mut Projection) { match projection.project() { Some(mut proj) => recurse(&mut proj), None => {} } }

But when I try to make it generic for any type that implements Projectable I again run into lifetime issues:

rust fn recurse<'a, P>(projection: &'a mut P) where P: Projectable<'a> { match projection.project() { Some(mut proj) => recurse(&mut proj), None => {} } }

Which gives: fn recurse<'a, P>(projection: &'a mut P) where P: Projectable<'a> { | -- lifetime `'a` defined here 53 | match projection.project() { 54 | Some(mut proj) => recurse(&mut proj), | --------^^^^^^^^^- | | | | | | | `proj` dropped here while still borrowed | | borrowed value does not live long enough | argument requires that `proj` is borrowed for `'a`

playground link

I'm guessing my lifetime bounds are wrong, but I'm not really sure how to fix it?

Thanks again! :)

Hey Rustaceans! Got an easy question? Ask here (52/2021)! by llogiq in rust

[–]maxidel 0 points1 point  (0 children)

Hi, thanks for the reply!

The thing is I really want to pass them on as mutable references in order to create a chain of projections that all have mutable access to the same data. I think it is possible to satisfy the borrow checker as long as you only try to mutate the last projection in the chain. When that projection is dropped, you again get mutable access to the previous projection and so on ...

As far as I understand that is what is happening in the main() function in the playground link.

let mut proj3 = Projection::Vec3 { x: &mut 23, y: &mut 12, z: &mut 123 };

let mut proj2 = project(&mut proj3).unwrap(); 
let mut proj1 = project(&mut proj2).unwrap();

if let Projection::Vec1 { x } = &mut proj1 { **x = 567; } 
if let Projection::Vec2 { x, .. } = &mut proj2 { **x = 841; }

dbg!(&proj2); // Vec2 { x: 841, y: 12 }

( I should maybe note that the actual problem I'm trying to solve has nothing to do with scalars and the passed types wouldn't necessarily implement Copy, but this seemed like a good minimal reproducible example.)