Skip to content

Transmutation and repr(transparent) for types in dasp_sample #190

Open

Description

@chipnertkj

My use case description got a little lengthy, so I'm putting the question here before I get into it.

Would it be okay to do either of these for the non-standard sample types defined in dasp_sample (like I24):

  • add repr(C) or repr(transparent), or
  • also implement bytemuck::Pod (and Zeroable, probably through derive) behind a feature flag?

I found myself in a situation while working with dasp and cpal in a latency-critical context. While transporting and transforming a signal between multiple threads, it would be convenient for me to convert a slice of samples to a byte representation, and later convert it back, as a sort of narrow waist. I could instead lay out the code paths and have different buffer types per sample type, but that ends up requiring a bunch of generated/macro code and is less preferable in my use case.

My first thought was to use bytemuck so that I don't have to carelessly shuffle pointers myself. It's just plain scalars after all. Except it's not - cpal uses the I24 type as one of the sample formats, which is defined by dasp a newtype around i32, and it doesn't implement bytemuck::Pod, so I couldn't take advantage of the crate's safety guarantees.

At this point I considered submitting this issue, but stopped to see if I could get around it by defining my own newtype (that I would implement Pod on) that maps 1:1 to your implementation and then use a little unsafe code for conversion when I need a slice to be in the other type. I thought maybe I could rely on the layout of the struct, but only then did I realize none of the non-standard sample types have a stable layout, since they don't have repr(transparent) or repr(C).

Even considering there's only one field in the struct, to my understanding this would make such unsafe casts unsound. As such I don't really have a good solution for my problem outside of the naive implementation for each sample type, or ignoring the potential unsoundness.

Would any of the changes I propose work for this crate? I'd be happy to open a PR if this seems like a reasonable direction.

Activity

  1. added a commit that references this issue on Aug 8, 2025
    844464f
  2. chipnertkj commented on Aug 8, 2025

    @chipnertkj
    Author

    I put more thought into this while working on a PR. Let's take I24 as an example again.
    Zero-copy transmute into [u8] would be useful for byte buffers as a narrow waist, as well as for communication with drivers, for anyone using dasp without cpal.

    The problem in my mind is: while, yes, I24 does, from the compilers perspective, have no padding etc., (which makes it reasonable to declare #[repr(transparent)],) the inner type is kept private. This is to keep the last 8 bits, which do, in a sense, semantically work as padding, zeroed at all times. Exposing the data through bytemuck carelessly could introduce confusion - the consumer might not realize the additional byte is there, or might assume that it is not relevant at all. At least with transparent on its own, you have to use unsafe, so knowing the underlying type and how it's used is a prerequisite.

    The counterargument is that in many cases you might simply transmute back into the original type, so this subtlety might not make a significant difference.

    This realization makes it more difficult for me to make a decision on whether including bytemuck is the right path. It is a question of balance, maintainability and I suppose preference. If included, it would most definitely require more explicit documentation somewhere.

  3. changed the title [-]Casting and `repr(transparent)` or `repr(C)` for types in `dasp_sample`[/-] [+]Transmutation and `repr(transparent)` for types in `dasp_sample`[/+] on Aug 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions