Gonna briefly go on a tangent here, but not without reason:
Rust lacks enum variant types. This is a language limitation. If you've used TypeScript before, then you might be familiar with the concept of type unions. If enum variants had dedicated types, then the same variant could be part of multiple enums. The common way to emulate this in Rust is basically:
enum MyEnum {
Variant1(Variant1),
Variant2(Variant2),
}
struct Variant1 {
// ...
}
struct Variant2 {
// ...
}
Which is painfully verbose, but again: It is a workaround for a language limitation.
Going back to cpal now: If Rust enums were type unions, then our (current) variant types for the ErrorKind enum would be unit structs. But the point here is that having the same variant in 2 enums shouldn't be thought of as having 2 distinct values representing the same thing, but rather as the same type being used in 2 places. The benefit is functional: It tells you which errors can occur at a given callsite. The drawback is an implementation detail. It introduces a lot of boilerplate code because Rust lacks the ability to express type unions concisely.
To get a better feeling for how to weight this tradeoff, I've tried to roughly sketch out the current error landscape:
I probably missed a few cases (do let me know), but the general idea would be something like:
Before:
struct Error {
kind: ErrorKind,
message: Option<Cow<'static, str>>,
}
enum ErrorKind {
DeviceBusy,
DeviceChanged,
DeviceNotAvailable,
HostUnavailable,
InvalidInput,
PermissionDenied,
RealtimeDenied,
ResourceExhausted,
StreamInvalidated,
UnsupportedConfig,
UnsupportedOperation,
BackendError,
Other
}
After:
struct Error<Kind> {
kind: Kind,
message: Option<Cow<'static, str>>,
}
enum HostErrorKind {
HostUnavailable,
General(General)
}
enum DeviceErrorKind {
DeviceNotAvailable,
UnsupportedOperation,
General(General)
}
enum BuildStreamErrorKind {
DeviceBusy,
InvalidInput,
UnsupportedConfig,
Device(DeviceErrorKind)
}
enum StreamErrorKind {
StreamInvalidated,
RealtimeDenied,
DeviceChanged,
General(General)
}
enum General {
PermissionDenied,
ResourceExhausted,
BackendError,
Other
}
Naming TBD
Gonna briefly go on a tangent here, but not without reason:
Rust lacks enum variant types. This is a language limitation. If you've used TypeScript before, then you might be familiar with the concept of type unions. If enum variants had dedicated types, then the same variant could be part of multiple enums. The common way to emulate this in Rust is basically:
Which is painfully verbose, but again: It is a workaround for a language limitation.
Going back to cpal now: If Rust enums were type unions, then our (current) variant types for the
ErrorKindenum would be unit structs. But the point here is that having the same variant in 2 enums shouldn't be thought of as having 2 distinct values representing the same thing, but rather as the same type being used in 2 places. The benefit is functional: It tells you which errors can occur at a given callsite. The drawback is an implementation detail. It introduces a lot of boilerplate code because Rust lacks the ability to express type unions concisely.To get a better feeling for how to weight this tradeoff, I've tried to roughly sketch out the current error landscape:
I probably missed a few cases (do let me know), but the general idea would be something like:
Before:
After:
Naming TBD