Structs with const and non-const data in it

I’m trying to model the following problem, but I’m not sure how to structure my data.

  1. I have an industrial fieldbus which may contain a number of subdevices.
  2. Each Subdevice has a fairly arduous amount of SubdeviceConfiguration.
  3. There can be multiple of the same model of Subdevice, but each must be given a unique StationAddress (u16). It would be nice to be able to reuse the configuration if a user has many of the same model of subdevices.
  4. The ordering of the subdevices matters.
  5. The user should be able to define a NetworkConfiguration that describes the expected subdevices on the bus.
  6. The NetworkConfiguration should be const.
  7. The subdevices each report a variable bit length of process_data (not guaranteed to be divisible by 8), which can be modeled as a packed struct. The bit length of the process_data is defined in the SubdeviceConfiguration
  8. The user should be able to easily reference / read / write to the process_data for a specific subdevice which is kept up to date by regularly transfering ethernet frames between the host and the subdevice.

What i’ve come up with so far:

  1. I know I want the NetworkConfiguration to be const because I don’t want some big massive mess of global variable state. There are other libraries that have gone this route and they are very difficult to read / understand because of this. For this same reason I don’t want to put the process_data in the NetworkConfiguration.
  2. Perhaps I can take in a * const NetworkConfiguration and cut it up / distribute it among a number of []Subdevice where each Subdevice contains a * const SubDeviceConfiguration in addition to process_data.
  3. I could model process_data as a []u8 even though it could have a bit length anywhere between 1 and 65535. It is typically a multiple of 8 and typically less than 256. Which makes the inefficency of the []u8 representation not too horrible.
  4. To manipulate the process_data I could provide a function to retreive it and write to it using a packed struct using a generic function, and assert that the bitsize of the packed struct provided has the same size as what is configured in the SubdeviceConfiguration. Or I could add a type field to my SubdeviceConfiguration and really go deep into generic code.

So I personally would probably just try to document the constness in the variable name. That should be good enough in most cases.

But if you really want to enforce a constant struct field (well as far as enforcing is possible in a language that has const cast), then obfuscating the struct field and adding a getter is the easiest way to go about it:

const ... = struct {
    ...
    internal: [@sizeOf(NetworkConfiguration)] u8 align(@alignOf(NetworkConfiguration)),

    pub fn init(config: NetworkConfiguration) void {
        var self = ...
        @as(*NetworkConfiguration, @ptrCast(self.internal)).* = config;
    }

    pub fn getNetworkConfiguration(self: *...) *const NetworkConfiguration {
        return @ptrCast(&self.internal);
    }
};

If by const you mean the configuration is comptime known, then you can obviously stick that data into the struct as a comptime field. If the data in question is only known at runtime, you can use a const pointer pointing at a variable as comptime field instead.