GenTooMan has quit [Read error: Connection reset by peer]
GenTooMan has joined #rust-embedded
<re_irc>
<riskable> I have an interesting problem and I'm looking for ideas how to solve it: I have a struct "Config" that has "pub encoders: [EncoderConfig; 2],". The problem is I want this "2" to be dynamic and based on "num_encoders" that gets read earlier in the "Config.toml" (or even better: Detect how many the "[encoder]"s the user put in the config). It's sort of "chicken and egg" problem where I can put "{ config::<whatever> }" in...
<re_irc>
... structs everywhere _else_ but since this is within the same file I can't do that
<re_irc>
<riskable> I tried switching it to a "heapless::Vec" instead of just an array of fixed size but it turns out that Serde's "Deserialize" isn't supported by that (everything else in "heapless" seems to be though).
<re_irc>
<riskable> huw.: Yeah but that would require the end user set the number of encoders somewhere in the .rs files instead of the config
<re_irc>
<riskable> Basically, this is the "config_structs.rs" file that contains all structs related to configuration items... "build.rs" reads a "Config.toml" file and populates "config.rs" with the contents. That "config.rs" is sourced by several other files for things like const generics (e.g. "&[config_structs::EncoderConfig; { config::ENCODERS_NUM_ENCODERS }];"
<re_irc>
<riskable> Since "config_structs.rs" needs to exist _before_ "config.rs" I can't use a const generic value for "encoders"... Unless there's some other way I'm missing
<re_irc>
<riskable> I don't mind pre-allocating some value like, "8"
<re_irc>
<riskable> ...which was "the plan" when I tried to use "heapless::Vec" but then it wouldn't serialize/deserialize because that's not implemented for that type
<re_irc>
<GrantM11235> You should be able to calculate the size of the array at compile time with a const fn
<re_irc>
<riskable> GrantM11235: Interesting. I'll look into that right now. Do you have a decent (applicable) example?
<re_irc>
<GrantM11235> I hope that is at least a little applicable for you
<re_irc>
<riskable> GrantM11235: Yeah that helps but I can also see that it won't work since I can't know the length of the number of encoders from inside "config_structs.rs" since that gets figured out by "build.rs" (which imports "config_structs.rs"... Unless I can read the "Config.toml" from within a "const fn"? Hmm
<re_irc>
<riskable> No, I can't because then "config_struct.rs" would need "std"
<re_irc>
<GrantM11235> If you know the length in build.rs, you should be able to define a const that you can read from you main program
<re_irc>
<GrantM11235> also, you can use the "include_str" or "include_bytes" macro to include a file in your program as a "&str" or "&[u8]"
<re_irc>
<GrantM11235> ie in your build script, write "const MY_SIZE: usize = {calculated_size}" to a file called something like "generated.rs", then use "generated::MY_SIZE" as your array length
<re_irc>
<riskable> GrantM11235: I like this idea... I've already got my "build.rs" creating a "userconfig.rs" which gets used via "include!()" inside "config.rs". What I think I could do (based on your suggestion) is have some logic that runs before everything else inside "build.rs" that generates something like "const_lengths.rs" and pre-populates the top of "config_structs.rs" via another "include!()" line. It'll be a bit convoluted...
<re_irc>
<GrantM11235> Do you need to use the "include!" macro? Can't you just "use userconfig;" the file as a normal module?
<re_irc>
<riskable> GrantM11235: Well this doesn't work because of the same reason I can't use "heapless::Vec" (sigh)... (the trait bound "[EncoderConfig; ENC]: Serialize" is not satisfied)
<re_irc>
<riskable> (where ENC is from "pub struct Config<const ENC: usize>")
<re_irc>
<riskable> GrantM11235: Well I tried that but the "userconfig.rs" ends up being spat out to "$OUT_DIR/userconfig.rs" which ends up... I don't even know where LOL
<re_irc>
<riskable> Well that looks like it'd _kinda_ work but now I need to figure out how to make Serde happy... "the trait bound "&'static [EncoderConfig]: Deserialize<'_>" is not satisfied"
<re_irc>
<dirbaio> on build.rs, use Vec, deserialize into that
<re_irc>
<dirbaio> then format it with "{:?}"
<re_irc>
<dirbaio> and on the firmware side use `*
<re_irc>
<dirbaio> * "&'static [..]"
<re_irc>
<riskable> dirbaio: Yeah I was trying to avoid pulling the "encoders" out of "pub struct Config {}" but I might have to since I can't know the number of encoders the user put in the "Config.toml" before "build.rs" runs
<re_irc>
<dirbaio> yeah..
<re_irc>
<riskable> dirbaio: The problem with deserializing into a Vec is that you get "Value()" and "Integer()" structs that don't specify the integer type
<re_irc>
<riskable> I suppose I could just make literally everything a "u32" and then I wouldn't have to worry about that
<re_irc>
<dirbaio> yeah
<re_irc>
<dirbaio> I had to write 2 versions of the structs: one serde-friendly, one no-alloc-friendly
fabic_ has quit [Ping timeout: 256 seconds]
<re_irc>
<riskable> dirbaio: GrantM11235 huw. I finally got it working by just telling Serde to skip deserialization on the encoders array:
<re_irc>
#[serde(skip_deserializing)]
<re_irc>
pub encoders: &'static [EncoderConfig],
<re_irc>
<riskable> Then in my "build.rs" I assembled the static struct like so:
<re_irc>
let encoders_arr = config_table["encoders"].as_array();
<re_irc>
<dirbaio> why not use std Vec?
<re_irc>
<riskable> config_table is the same "Config.toml" but read in as a Serde "Table" instead of using my "Config" struct. So in essence it converts the .toml file twice
<re_irc>
<riskable> dirbaio: ...cuz this is "[no_std]"?
<re_irc>
<dirbaio> but you're doing the serde thing in build.rs right? that one has std available
<re_irc>
<riskable> Yeah but the output is available and read by all the other code. This is what it looks like:
<re_irc>
<riskable> "config_table["keyboard"].get(fname);" works whether I use Serde's format or a Vec 🤷
<re_irc>
<riskable> dirbaio: That would require maintaining two different copies of "config_structs.rs" though
<re_irc>
<dirbaio> in build.rs, deserialize the entire "Config" with "serde", and convert it to Rust source with "format!("{:?}", config)"
<re_irc>
<dirbaio> it does the whole array for you
<re_irc>
<riskable> BTW: "Value" and "Table" mean the same thing from this perspective
<re_irc>
<dirbaio> I have a similar thing here
<re_irc>
<riskable> dirbaio: Do you maintain two different versions of all your structs? One for "build.rs" and one for everything else?
<re_irc>
<riskable> I mean, I thought about doing that early on but decided it would be "easier" (hah!) to just have one .rs file with all my configuration structs
<re_irc>
<dirbaio> but the flip side is you can load the thing with a single serde call, and convert it to rust with a single "format!("{:?}", config)" call
<re_irc>
<dirbaio> the whole thing, nested arrays and everything
<re_irc>
<dirbaio> no need to manually loop and add strings together
<re_irc>
<dirbaio> I wish "{:?}" printed enums correctly, but no :P
<re_irc>
<riskable> dirbaio: I'm actually going through now and changing my code to use your way (mostly) 👍
<re_irc>
<riskable> Rather than do the whole "Config" as a "format!()" call I'm going to do each individual section that way. Otherwise I don't think I can get the "Encoders" part working haha
<re_irc>
<dirbaio> 🤔
<re_irc>
<dirbaio> I think it should Just Work
<re_irc>
<dirbaio> if you use a slice instead of an array: "&'static [EncoderConfig]"