Design Question: Large Parameter List

Hi Everyone,

I've been going through a mental exercise in my head (and some small prototypes) over how I would rewrite some existing software in Rust. Its unlikely to happen but it has been a useful learning experience.

Part of this program has a TCP interface over which different parameters can be set. I think the list of parameters as a whole is probably around 100. There are maybe 6 or 7 logical groupings in there that actual apply to different modules.

So lets suppose we have 6 structs which have structured parameters and this general interface which takes a name and a parameter value. How do you join them. I can think of:

  1. A massive match statement, handling for each possible parameter input.
  2. Some kind of module representation where each module registers its parameters in a Hashmap so when we get a parameter we can look up the module that needs it and then somehow dispatch it to the module to handle. Not sure how the lookup would work. Either need a another match to lookup module boy name or store Rc of the modules.
  3. Similar to 2 but somehow storing a function to be called in each case.

Is there another approach I might be missing? Or a clear winner in what I have suggested above?

It is probably worth adding I'm not overly concerned with performance here, more expandability. Essentially I think it will function like a builder - when a command that needs these parameter values is called the current state will be captured and used.

This sounds like a good case for the builder pattern. The core idea is that your structs are given sane default values, and the builder methods can be used to override those. When all values have been set, the builder can be finalized to return a struct that is usable.

For organization, I think it does make sense to break it into smaller logical pieces. They may even be hierarchical, and the “defaultness” can remove a lot of boilerplate for setup if you break it up appropriately.

Here’s a very quick introduction to the builder pattern if you aren’t familiar with it: Builder - Rust Design Patterns

4 Likes

Thanks @parasyte - you are right it is similar to the builder pattern.

The issue I still have though is the parameters are coming over the wire in a generic "set parameters" command so I can't hardcode the setting calls. Instead I need to take a string definition and defined it from there.

Here is an example of the match statement version (which works but I wouldn't like to write it for 100 params!)

struct Mod1 {
    param1: u32,
    param2: u32
}

struct Mod2 {
    param1: u32
}

struct system {
    mod1: Mod1,
    mod2: Mod2
}


impl system {
    fn set_parameter(&mut self, name: &str, value: u32) {
    
        match name {
            "param1" => self.mod1.param1 = value,
            "param2" => self.mod1.param2 = value,
            "otherparam1" => self.mod2.param1 = value
        }
        
    }
}

As the parameter names will sometimes change, there's not much you can do about listing 100 (string, id) pairs in the source code. A single-use macro can help reduce the amount of typing this requires. For example:

macro_rules! impl_system_params {
    ( $($mod:ident { $($wire:pat => $attr:ident),* } )* ) => {
        impl system {
            fn set_parameter(&mut self, name: &str, value:u32) {
                match name {
                    $($(
                        $wire => self.$mod.$attr = value,
                    )*)*
                    _ => unimplemented!()
                };
            }
        }
    }
}

impl_system_params! {
    mod1 {
        "param1" => param1,
        "param2" => param2
    }
    mod2 {
        "otherparam1" => param1
    }
}

If you need to dynamically create a data structure based on a string, then that's called deserialization/unmarshaling. What format do the parameters come in? Maybe there's already a Serde-supporting serialization library for it.

3 Likes