ChanServ changed the topic of #rust-embedded to: Welcome to the Rust Embedded IRC channel! Bridged to #rust-embedded:matrix.org and logged at https://libera.irclog.whitequark.org/rust-embedded, code of conduct at https://www.rust-lang.org/conduct.html
dc740 has quit [Remote host closed the connection]
causal has joined #rust-embedded
starblue has quit [Ping timeout: 268 seconds]
starblue has joined #rust-embedded
<re_irc> <thebutlah> Quick tip for people looking to write generic code - use the amogus pattern ™
<re_irc> The amogus pattern ™ lets you write cfg-gated modules that do initial platform-specific setup, and return generic (i.e. impl Trait) types that you can use in your hardware-independent code.
<re_irc> For example:
<re_irc> <thebutlah> Quick tip for people looking to write generic code - use the amogus pattern ™
<re_irc> The amogus pattern ™ lets you write cfg-gated modules that do initial platform-specific setup, and return generic (i.e. impl Trait) types that you can use in your hardware-independent code.
<re_irc> For example:
<re_irc> #[cfg(feature = "mcu-esp32c3")]
<re_irc> <thebutlah> +calling "get_peripherals" gives you all your peripherals abstracted behind ehal traits
<re_irc> <thebutlah> Quick tip for people looking to write generic code - use the amogus pattern ™
<re_irc> The amogus pattern ™ lets you write cfg-gated modules that do initial platform-specific setup, and return generic (i.e. impl Trait) types that you can use in your hardware-independent code.
<re_irc> For example:
<re_irc> #[cfg(feature = "mcu-esp32c3")]
<re_irc> <thebutlah> Quick tip for people looking to write generic code - use the amogus pattern ™
<re_irc> The amogus pattern ™ lets you write cfg-gated modules that do initial platform-specific setup, and return generic (i.e. impl Trait) types that you can use in your hardware-independent code.
<re_irc> For example:
<re_irc> #[cfg(feature = "mcu-esp32c3")]
<re_irc> <thebutlah> * this inside a cfg-gated amogus module:
<re_irc> <thebutlah> Quick tip for people looking to write generic code - use the amogus pattern ™
<re_irc> The amogus pattern ™ lets you write cfg-gated modules that do initial platform-specific setup, and return generic (i.e. impl Trait) types that you can use in your hardware-independent code.
<re_irc> For example:
<re_irc> #[cfg(feature = "mcu-esp32c3")]
<re_irc> <thebutlah> Quick tip for people looking to write generic code - use the amogus pattern ™
<re_irc> The amogus pattern ™ lets you write cfg-gated modules that do initial platform-specific setup, and return generic (i.e. impl Trait) types that you can use in your hardware-independent code.
<re_irc> For example:
<re_irc> #[cfg(feature = "mcu-esp32c3")]
<re_irc> <thebutlah> * draw a clear boundary between generic trait based code and concrete platform/hardware specific
<re_irc> <thebutlah> * module™:
m5zs7k has quit [Ping timeout: 265 seconds]
m5zs7k has joined #rust-embedded
<re_irc> <luojia65> Hello! I need someone to review this pull request: https://github.com/rust-embedded/riscv/pull/112 Thanks!
<re_irc> <thebutlah> Does anyone know if there is a good no-alloc alternative to "eyre" or "anyhow"?
<re_irc> <thebutlah> or is the concept just fundamentally impossible and I should use "thiserror"?
<re_irc> <MathiasKoch> Followup to ^: Has anyone made a version of thiserror that implements "defmt::Format"?
<re_irc> <ub|k> yeah, i was also looking for that at some point
<re_irc> <ub|k> didn't find anything
<re_irc> <James Munns> that would be awesome :D
starblue has quit [Ping timeout: 265 seconds]
starblue has joined #rust-embedded
jr-oss has quit [Remote host closed the connection]
jr-oss_ has joined #rust-embedded
<re_irc> <chemicstry> I have a problem where I measure values and need to report state [critical_low, low, ok, high, critical_high] with hysteresis between them. And it turned out much more difficult than I thought. For each update you have to recalculate ranges depending on which side of hysteresis the current state is and compare each. So far my solution was just a horrible mess of ifs/matches. Has anyone encountered a similar problem? Maybe there is some...
<re_irc> ... magic algorithm/crate for problems like this?
<re_irc> <jessebraham> thebutlah: You _can_ use anyhow in "no_std", but you have to manually call ".map_err()" instead of using "?". I think this is because it relies on "std::error::Error" otherwise, so with that being moved into "core" maybe one day you will be able to use "?"?
<re_irc> <therealprof> chemicstry: Why not create an "enum" representing the state and an update function on that enum where you pass in the new value to update the state according to your rules?
<re_irc> <therealprof> Rust is a rather nice language to express those state machines.
<re_irc> <chemicstry> Yes, implementing it on enum is the end goal, but I had problems expressing the state machine, since any state can transition into any other state. I eventually came up with something like this https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d0f6c6fbf8231eb8bcd369b56773ade1. It tests if it can go up in state where hysteresis is subtracted and if not, tests if it can go down in state where hysteresis is added....
<re_irc> ... Not sure if it's the most efficient solution but it seems to work
<re_irc> <therealprof> Uhm, you kinda lost me there with the code. 😅
<re_irc> <therealprof> I was expecting to see an "enum" with the possible states and some code which checks (depending on the current state) whether the change into the new state is possible.
<re_irc> <chemicstry> sorry about the confusing code :D so enum and match statements are nice to express "regular" state machines, but in this case any state can transition into any other state, which means that each match arm would have 4 if statements and hysteresis calculations. So for 5 states, you have 20 if statements already. Unless you limit transitions to neighbor states and run state machine with the same value until no transition ocurs. What I...
<re_irc> ... did instead is a generalized solution over any number of steps
<re_irc> <ithinuel> How about the doc then? It's no longer on master as far as I can see.
<re_irc> <therealprof> chemicstry: I don't get the part with the 4 if statements. Can you show the code? Maybe we can make it nice. 😉
<re_irc> <chemicstry> if you have 5 states, where each can transition into any other, then you have 4 possibilities for transition, hence 4 if statements
<re_irc> <chemicstry> +in each match arm
<re_irc> <therealprof> I can understand if you have 5 arms, but where are those if statements coming from? Are the if statements the same in every arm?
<re_irc> <chemicstry> sorry I don't want to waste your time, but if you are interested I can quickly write an example
<re_irc> <therealprof> Well, you asked for help and some people here enjoy puzzles... 😄
<re_irc> <dkhayes117> I've played with state machines with enums some 🙂
<re_irc> <therealprof> dkhayes117: Ooh, dual state. 😉
<re_irc> <therealprof> That's kind of what I had in mind but with new numbers passed in instead of discrete events.
GenTooMan has quit [Ping timeout: 244 seconds]
GenTooMan has joined #rust-embedded
<re_irc> <chemicstry> ok, here is an example https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3d13bdfd3b21382c7f48c31d5d7dd660 still missing 2 states, but you get the point
<re_irc> <dkhayes117> I also implemented it with the Ana Hoverbear approach with unit structs, wrappers, a parent enum, and the from methods. It was just too much boiler plate for me.
<re_irc> <therealprof> chemicstry: Why are do doing 4 steps at once instead of looping over the update function 4 times with a single value?
<re_irc> <chemicstry> well you could, but it's still 2 if statements per branch, 10 in total
<re_irc> <chemicstry> * state,
<re_irc> <chemicstry> (exlcuding first and last state)
<re_irc> <chemicstry> also looping the update function until nothing changes seemed a bit off. Also if the hysteresis is incorrect you could end up in an infinite osciallting loop, needing an additional iteration limit
<re_irc> <chemicstry> * If
<re_irc> <therealprof> Well, you could add an Invalid state to foul out.
<re_irc> <therealprof> Also I noticed that the ifs are mostly identical so instead of putting it into the code block you can also put the condition into the match arm and match multiple cases... Like:
<re_irc> EnvState::CriticalHigh | EnvState::High | EnvState::Normal
<re_irc> {
<re_irc> if val < steps[0] - hysteresis =>
<re_irc> <chemicstry> well yeah, I guess you could optimise it a bit more, but it's still a lot of manual conditions and room for mistakes, what seems like a simple problem. Adding an additional state/step to such code is a headache
<re_irc> <chemicstry> I was looking if there is some simple generalized solution for multiple value ranges with hysteresis
GenTooMan has quit [Ping timeout: 248 seconds]
<re_irc> <therealprof> Hm, to me this sounds like a straight forward implementation; easy to write, extend, test and debug. Not sure how much easier this could get, at least not in terms of obviousness and maintainability.
<re_irc> <therealprof> In C/C++ something like this would become very painful very rapidly.
<re_irc> <chemicstry> It would become painful at the cmake/makefile stage 😆
GenTooMan has joined #rust-embedded
causal has quit [Quit: WeeChat 3.6]
GenTooMan has quit [Ping timeout: 260 seconds]
GenTooMan has joined #rust-embedded
dc740 has joined #rust-embedded
<re_irc> <thebutlah> jessebraham: doesn't it still use "Box" though?
<re_irc> <thebutlah> I guess primarily I'm wondering if its even possible to do trait object based errors without "alloc"
<re_irc> <jessebraham> I'm not sure I've never actually used it in "no_std", but in the README they mention it and I don't recall seeing anything about "alloc". I could have missed it, though.
<re_irc> <thebutlah> it still requires "alloc" - so did "eyre"
<re_irc> <thebutlah> because the trait objects get boxed
<re_irc> <jessebraham> Ahh alright my bad haha
cr1901_ is now known as cr1901
<re_irc> <mattjperez> thebutlah: Funny coincidence. There's a smolbox crate with nostd support.
<re_irc> <ithinuel> But it is still published under https://docs.rust-embedded.org
<re_irc> should this part be updated or unpulished? 😧
dc740 has quit [Remote host closed the connection]
<re_irc> <thebutlah> > fallback to heap for large item
<re_irc> > If std feature flag is opted out, alloc crate will be linked, which requires nightly rust.
<re_irc> <thebutlah> I'm not sure this solves the problem of no "alloc"
<re_irc> <thebutlah> I think really it might just not be possible to use trait objects because they must be either unsized or boxed
<re_irc> <thebutlah> I wonder if using unsized values, or using a clever macro to generate sized enums, would be viable
<re_irc> <thebutlah> like basically emulate dyn trait object errors, but with enums
<re_irc> <thebutlah> +so that it can be sized without "alloc"
<re_irc> <James Munns> your types would probably get REALLY gnarly.
<re_irc> <dirbaio> you can totally return owned "dyn"s inside some kind of "fixed size box" like that
<re_irc> <dirbaio> but it'd have to panic if the error is too big... and since people will want to "wrap" errors when propagating them up the stack. it'll indeed panic too often :D
<re_irc> <dirbaio> plus perf and codesize wouldn't be great...
<re_irc> <thebutlah> This sounds kind of cursed, but what if there was an "archetype" for every trait object where we "std::mem::leak" the first instance, and going forward future instances of that type (determined by "Any") overwrite whatever data was leaked. So you could store each trait object in the leaked slot for it, which is appropriately sized and used via reference
<re_irc> <dirbaio> for embedded, error enums is the way...
<re_irc> <dirbaio> +plain old
<re_irc> <dirbaio> "Any" is super bloated too. 64bit typeids and vtables everywhere
<re_irc> <thebutlah> ok, then what if we made it so that there was a trait that identified the kind of error using a smaller value like a u16. And we blankel impl that trait for anything that implements "core::error::Error"
<re_irc> <thebutlah> +using a global counter somehow (at compile time?)
<re_irc> <dirbaio> cursed :D
<re_irc> <thebutlah> yes :D
<re_irc> <thebutlah> but possibly efficient?
<re_irc> <dirbaio> no idea how you'd do that global counter
<re_irc> <thebutlah> yeah, I'm not sure - it feels like it would require compile-time heap allocation to keep a counter reference around
<re_irc> <thebutlah> * around. But thats not a thing
<re_irc> <thebutlah> Maybe I'll tinker with this idea at some point
<re_irc> <dirbaio> defmt does something like this (assign a unique id to every logged string at compile time)
<re_irc> <dirbaio> with horrible linker tricks
<re_irc> <thebutlah> but even if you manually assigned the TypeIDs by implementing the trait yourself, you could use those typeids to potentially to get around the slowness of "std::any::Any"
<re_irc> <thebutlah> and once you have them, then you can do things like keep a global slice of "[&dyn my::custom:error::Trait]"
<re_irc> <dirbaio> but, for returning errors, you don't want to return just which type
<re_irc> <dirbaio> but also the actual value
<re_irc> <dirbaio> for exapmle if you have an error enum with multiple cases, you want to know which one it is
<re_irc> <thebutlah> yes, you would get the typeid, and store your error type via "core::mem::replace" on the global slot for it
<re_irc> <thebutlah> and what you return is a sized handle that gracefully reads from that global slot
<re_irc> <thebutlah> +via "Deref"
<re_irc> <dirbaio> you still have to assign some size to that "global slot", and get panics if an error is bigger than that
<re_irc> <dirbaio> and you'll have race conditions on that global slot if you have multiple threads
<re_irc> <dirbaio> it's like the global "errno"
<re_irc> <thebutlah> dirbaio: yes, at program start (or maybe lazily on first encountering an error of this kind) you would leak the error
<re_irc> <thebutlah> * error. That gives you the global slot
<re_irc> <dirbaio> there's no guarantee the first error you allocate is the biggest
<re_irc> <thebutlah> * statically (at program start), or maybe lazily on first encountering an error of this kind,
<re_irc> <thebutlah> correct but wouldn't it not matter? each error type gets its own slot, and you only access those slots via reference
<re_irc> <dirbaio> ah, per type
<re_irc> <thebutlah> you could leak _every_ error but thats a lot of leaked memory, so instead you "replace" the slot for every new error of that type
<re_irc> <thebutlah> * error of that same
<re_irc> <thebutlah> * "mem::replace"
<re_irc> <dirbaio> you still have the threads issue
<re_irc> <dirbaio> it's like "errno", which today is universally agreed it was a giant design mistake
<re_irc> <thebutlah> you could use thread local storage maybe? Probably not too bad to have the memory usage scale with number of cores. IDK just spitballing
<re_irc> <dirbaio> thread local storage, on embedded, with no OS :P
<re_irc> <thebutlah> dirbaio: I feel like "errno" is bad because its non-local errors, but in this case you are actually returning your error, which just is a struct around a reference
<re_irc> <thebutlah> * reference, and you can't access the global slot without that wrapper struct
<re_irc> <dirbaio> hm
<re_irc> <dirbaio> you don't even need threads for this to break
<re_irc> <thebutlah> its basically assuming any function has a "&mut ref" to this slot, which I think violates the borrow checker
<re_irc> <dirbaio> call a function, save the error reference in "err1"
<re_irc> call another, which will overwrite the global slot
<re_irc> use "err1" afterwards, now it os an entirely different error
<re_irc> <thebutlah> there would need to be some guarantee that only one &mut ref is held at a time
<re_irc> <dirbaio> * 's
<re_irc> <thebutlah> and ideally enforced statically not dynamically
<re_irc> <thebutlah> not sure how to do that
<re_irc> <thebutlah> will think more
<re_irc> <dirbaio> doesn't sound very possible
<re_irc> <dirbaio> try implementing it if you can :D