parallelsystems / bit-struct

ergonomic bitfields in Rust
12 stars 3 forks source link

Unable to use bit structs in a read-only context. #17

Open Borketh opened 1 year ago

Borketh commented 1 year ago

Good afternoon! I've been writing something heavily using this library but I came across a roadblock.

Problem

The way GetSet are created means that I need mutability of a bit struct to be able to read from it, which is not necessary nor possible in some cases.

Example

use bit_struct::*;

bit_struct! {
    // Useless struct for demonstration purposes
    pub struct Halfling(u8) {
        first_half: u4,
        second_half: u4
    }
}

impl Display for Halfling {
    // note immutability of self
    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
        writeln!(f, "The halves together make {}", self.first_half.get().value() + self.second_half.get().value())
    }
}

This will not compile because .first_half() and .second_half() take &mut self to make their GetSets, which the signature of Display::fmt doesn't permit. In this use case I don't need to modify the Halfling at all and can't, but it insists it must be mutable.

Solution?

I understand why GetSet needs to be constructed with a mutable reference, so I propose a Get or similarly named struct with read-only privileges that will work in this context. How that would work, I'm not sure.

By convention it would make sense to change all generated field GetSet functions to mut_[field name] and make the existing ones return Get structs instead, but this would break everything wholesale. How would you prefer to implement this? I would be willing to do the legwork in a PR.

andrewgazelka commented 1 year ago

When I started making bit-struct, I tried to make everything be in the form self.get().first_half() and self.set().first_half(set_value) to avoid this problem, but I ran into issues (I forgot what).

You can always copy Halfling and do GetSet on it, although is it not ideal.

honestly, I'd argue that since proc macros are much better supported in IDEs we could use proc macros for a lot of this and release a major breaking change where we rework everything. What are your thoughts regarding this?

Borketh commented 1 year ago

So you mean like this?

use bit_struct::*;

impl Display for Halfling {

    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
        let temp = &mut self.clone();
        writeln!(f, "The halves together make {}", temp.first_half.get().value() + temp.second_half.get().value())
    }
}

I mean it does work but it does look a little funky.

I don't see how proc macros could help here, but then again I'm not experienced in writing them at all, so if you have an idea go for it! I can't think of a reasonable syntax for separating get and set just yet but I think I'll sleep on it :)

andrewgazelka commented 1 year ago

So you mean like this?

use bit_struct::*;

impl Display for Halfling {

  fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
      let temp = &mut self.clone();
      writeln!(f, "The halves together make {}", temp.first_half.get().value() + temp.second_half.get().value())
  }
}

I mean it does work but it does look a little funky.

I don't see how proc macros could help here, but then again I'm not experienced in writing them at all, so if you have an idea go for it! I can't think of a reasonable syntax for separating get and set just yet but I think I'll sleep on it :)

I think the issue with not being able to have a Get and a Set half was something related to it being really hard/anoying to do without proc macros, but again... it's been a long time since I made this

Also I think you can do

fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
        let temp = *self;
        writeln!(f, "The halves together make {}", temp.first_half.get().value() + temp.second_half.get().value())

which is slightly cleaner

Borketh commented 1 year ago

That does work for the time being. I'll let you know if I have any other ideas.