3 ms·
I don't quite follow what the author is saying. Perhaps I'm missing something about Go. Also, I'm not aware if Go has a way to make stack-only structs which are
by elcritch 2y ago
I don't quite follow what the author is saying. Perhaps I'm missing something about Go. Also, I'm not aware if Go has a way to make stack-only structs which aren't pointers. To me it seems more like they're just indexing off the Rust philosophy that zero-values are inherently unsafe and must not be allowed at a language level.
Really I've never been convinced that default values are the huge security or safety issues they're made out to be. Perhaps there's a bunch of CVEs due to it but all the CVE's I've ever read are due to memory bounds issues, use after free, or not zeroing out data properly and leaking info. Also I'm talking about languages where all values are properly initialized to a known zero state. Not C's worst case where a value could be full of random data.
Sure there's some scenarios, say encryption keys, where default values could be an issue. However a programmer can just as easily set an encryption key to `default()` in Rust and end up with the same issue. It may be "explicit" but programmers get in habits of using things like `default()` and so it's still as likely to happen as it would with non-explicit defaults IMHO.
Requiring non-zero values isn't worth the mental overhead in the other 99.9% of cases where zero-values can simplify a lot of coding to say declare a stack array of zero-valued structs or unions, etc. In those cases you can make the zero-value of a union type where the zero value is `UnInitializedBadEncryptionKey`.
> As noted in a comment by loreb on my entry on how union types would be complicated, these 'union' or 'sum' types in Go also run into issues with their zero value, and as Ian Lance Taylor's issue comment says, zero values are built quite deeply into Go.
What are the actual issues though with zero values in Go when used with union types?
> You can define semantics for union types that allow zero values, but I don't think they're really particularly useful for anything except cramming some data structures into a few more bytes in a somewhat opaque way, and I'm not sure that's something Go should be caring about.
Why not? Generally the zero-value for a union type is just the first option, which can be useful or not. You can wrap it in an option type to ensure it's been initialized too.
If Go's semantics mean it's always a nullable pointer, then that still doesn't make union types useless. There's still value in knowing that "if there's a value, it's one of these Y options".
Though it does make sense that in Go that type assertions in Go could be used at the compiler level to say that "this variable is one of Y interfaces".
- aw1621107 2y ago> To me it seems more like they're just indexing off the Rust philosophy that zero-values are inherently unsafe and must not be allowed at a language level. I don't think that "zero-values are inherently unsafe" is a Rust philosophy? I think it's more about some sense of control and/or design - Rust shouldn't force types to have a zero value if a zero value doesn't make sense (e.g., references, NonNull<T>), and Rust shouldn't force your type to have zero as its default value if another default would be more appropriate. > However a programmer can just as easily set an encryption key to `default()` in Rust and end up with the same issue. It may be "explicit" but programmers get in habits of using things like `default()` and so it's still as likely to happen as it would with non-explicit defaults IMHO. default() would only be usable if the encryption key type implements Default and I think I'd be a bit on the skeptical side with respect to how likely Default would be "accidentally" implemented on it. Putting aside questions about how Default on an encryption key made it into the codebase in the first place, IIRC array types for representing crypto keys are not that uncommon and I don't think Default is implemented for arrays (by default?), so #[derive(Default)] wouldn't even compile and I'm not sure impl Default would be common enough to be something that's done out of habit. Specific example aside, I'm not Default is one of those traits that are habitually added to structs in the first place? > Requiring non-zero values isn't worth the mental overhead in the other 99.9% of cases where zero-values can simplify a lot of coding to say declare a stack array of zero-valued structs or unions, etc. Would it really be that much simpler? For example, in Rust for Copy types: [DefaultStruct::default(); SIZE] vs. [NonDefaultStruct { value: defaultValue }; SIZE] // other options available as well I'm also not sure whether instances where zero values simplify things outweigh the ability to more accurately model your domain in the type system. Sure, declaring a stack array of zero-valued structs/unions might be simpler, but I feel being able to more accurately model your intended domain by making invalid states unrepresentable would be more generally useful. > In those cases you can make the zero-value of a union type where the zero value is `UnInitializedBadEncryptionKey`. The disadvantage to that approach is that now everything that deals with that union type needs to know about/deal with uninitialized values even if such values are only ever transient during initial construction and are supposed to be impossible otherwise. It's arguably cleaner to make such a state unrepresentable at all and use a different mechanism to represent being uninitialized. For example, Rust again: let uninit = [const { MaybeUninit<NonDefaultStruct>::uninit() }; SIZE]; // initialize as appropriate let init = unsafe { std::mem::transmute<_, [NonDefaultStruct; SIZE]>(uninit); } And now all code that deals with NonDefaultStruct knows for sure it has been properly initialized.