You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Transmutation and repr(transparent) for types in dasp_sample #190
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.
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.
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
Activity
Enable safe sample transmutation; add bytemuck
chipnertkj commentedon Aug 8, 2025
I put more thought into this while working on a PR. Let's take
I24as 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 usingdaspwithoutcpal.The problem in my mind is: while, yes,
I24does, 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 throughbytemuckcarelessly 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 withtransparenton its own, you have to useunsafe, 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
bytemuckis 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.[-]Casting and `repr(transparent)` or `repr(C)` for types in `dasp_sample`[/-][+]Transmutation and `repr(transparent)` for types in `dasp_sample`[/+]