c tp s: we tested in production, again. i love thing called "agile"
|
|
![]() |
|
![]()
|
# ? Feb 9, 2025 14:25 |
|
Zlodo posted:maybe dont allow to do this with a struct with no default ctor? so now i can't put these structs in any kind of collection, or would you want some magic in the compiler that allows the standard library lists and maps etc. to work (even though that wouldn't help for any custom collection types)?
|
![]() |
|
If you think about it, production is just a really big integration test
|
![]() |
|
Jabor posted:lol we just got done talking about that
|
![]() |
|
Jabor posted:so now i can't put these structs in any kind of collection no, just not in arrays, and if you think about it it makes sense that you can't put something that have no meaningful default initialization in an array quote:or would you want some magic in the compiler that allows the standard library lists and maps etc. to work (even though that wouldn't help for any custom collection types)? c++ allows you to put something without a default ctor just fine in a list or map or even a vector if you do that with a vector trying to resize it without specifying an initialization value will try to to call the default ctor and won't compile but you can still push things in it (and also reserve space in advance for them)
|
![]() |
|
animist posted:If you think about it why would i do a thing lik ethat
|
![]() |
animist posted:If you think about it, production is just a really big integration test thing is, we have a fully functional test environment mirroring prod to a teat
|
|
![]() |
|
cinci zoo sniper posted:thing is, we have a fully functional test environment mirroring prod to a teat there are way more testers hitting prod, though
|
![]() |
|
C++ can do that because it allows uninitialized memory, which Java/C# promise to avoid. ![]()
|
![]() |
Fiedler posted:there are way more testers hitting prod, though exact same amount, actually. just with slight delay
|
|
![]() |
|
Zlodo posted:static factory methods are fine as long as you also make the ctor private ok now how do you raise an error
|
![]() |
|
Zlodo posted:no, just not in arrays, and if you think about it it makes sense that you can't put something that have no meaningful default initialization in an array so how would you implement your list type? in c++ you can say that parts of your array that you haven't initialized are just uninitialized garbage and you pinky promise not to read from them before you've overwritten them with an actual value. in c# the language can't rely on you pinky promising not to do anything wrong, so what's your alternative? use extra space at runtime to track which slots in the array have been properly initialised?
|
![]() |
|
I like rust's maybeuninitialised<t> or whatever it is - same runtime representation, and when you really have initialized it you cast it to the real values, but until you do it's an unsafe to try and access it. Implemented as an untagged Union with the unit type.
|
![]() |
|
And ten million times better than mem::uninitialised which was causing UB all over the place be being the most seductive use of unsafe and even better a whole bunch of blog posts on how to use it safely it that were often just wrong
|
![]() |
|
Jabor posted:in c# the language can't rely on you pinky promising not to do anything wrong c# actually has a keyword for this specific purpose
|
![]() |
|
Sapozhnik posted:ok now how do you raise an error MAGIC! No, seriously, just return a nullptr.
|
![]() |
|
The real way to do this is to just place the object on stack and throw an exception from constructor if the construction fails, but if you are doing dumb poo poo like heap-allocating by default, nullptr is a perfectly fine return value to signalize errors. (Also optionals and expecteds, which, unlike in Java, actually work the way they are supposed to)
|
![]() |
|
for something like a guid, you could just create a guid with the meaning uninitialised to use as a default value in collections etc. it could even be all zeroes. just make it a little harder to access this value by accident, while doing something you might reasonably expect to do something different, and the problem that motivated this whole discussion would go away.
|
![]() |
|
in fact, MaybeUninit<T> entered stable rust today
|
![]() |
|
i'll be the terrible programmer and say that i honestly don't understand what the practical case for an uninitialized guid is in c# is it just so you can preallocate a collection of them and defer the call to the rng to when you need to use them? if so that feels really weird. i have to assume the clr introduces enough of an overhead that the performance impact would be negligible... Blinkz0rz fucked around with this message at 23:08 on Jul 4, 2019 |
![]() |
|
When I saw that I immediately thought of your post. Finally, our code will be independent of initialization. ![]()
|
![]() |
|
Blinkz0rz posted:i'll be the terrible programmer and say that i honestly don't understand what the practical case for an uninitialized guid is in c# Consider that the most common use case when allocating an array of guids (or any other struct) is that you'll replace everything that's initially in the array by copying other instances over the top of them. Not only would creating hundreds of random guids be expensive, it would also be completely pointless because they'd never be used for anything.
|
![]() |
|
I'm not familiar with rust, and I feel like I'm missing something. what's the difference between this and like a std::optional<T>? Does this avoid the runtime cost of keeping track of whether there's a value or something?
|
![]() |
|
Illusive gently caress Man posted:I'm not familiar with rust, and I feel like I'm missing something. what's the difference between this and like a std::optional<T>? Does this avoid the runtime cost of keeping track of whether there's a value or something? Nah rust has that, it's called Option<T>. This is just a thing to make sure the compiler doesn't do crazy optimizations on your uninitialized values, vaguely like `volatile` in java (You can have a type-level Option<T> in rust using ownership semantics, it would prolly be kinda a pain to use though)
|
![]() |
Illusive gently caress Man posted:I'm not familiar with rust, and I feel like I'm missing something. what's the difference between this and like a std::optional<T>? Does this avoid the runtime cost of keeping track of whether there's a value or something? They are usually* different sizes and have slightly different semantics. Option<i32> is 5 bytes (probably +alignment), because it's got 4 bytes for the 32-bit integer plus an extra bit for the enum flag. MaybeUninit<i32> is 4 bytes, but the cost is that the user has to keep track of whether it's initialized or not on pain of undefined behavior. So yeah, exactly what you guessed: MaybeUninit<T> avoids the runtime and memory cost of keeping track of whether there's a value. *I say "usually different sizes" because there are cases where the rust compiler can optimize away the size overhead of an Option<T>. The classic example of this is that &i32 is a non-null pointer to an i32, and since the compiler knows it's non-null, and thus that 0 is an invalid value for &i32, it can use 0 as the "empty" value for Option<&i32>. So Option<&i32> has no size overhead.
|
|
![]() |
|
not a great idea to implement something as a struct when the zero value is both invalid and causes faults slowly and im sure this came up in a clr design discussion so how many freakin guids does p/invoke need that making a guid class was unacceptable overhead
|
![]() |
|
its me im the programmer who writes java classes that are basically read-only structs. seems safer to "final" everything until you know you have a real use-case that requires mutability
|
![]() |
|
Lutha Mahtin posted:its me im the programmer who writes java classes that are basically read-only structs. seems safer to "final" everything until you know you have a real use-case that requires mutability abso fuckin lutely, immutable objects are the way to go whenever you can get away with it. doing it with conveniences like builders and poo poo makes for a lot of boilerplate if youre not using a codegen thing but thats what ides are for
|
![]() |
|
autovalue and immutables exist to automate that for you using compile-time codegen builders are poo poo though because hey if i wanted an incomplete object initialization to explode at run time instead of compile time id use loving python i mean you kinda have to use them anyway but they're by far the worst thing about straight java
|
![]() |
|
Blinkz0rz posted:i'll be the terrible programmer and say that i honestly don't understand what the practical case for an uninitialized guid is in c# I'm feeling the same way about this whole conversation. Why not just make the default constructor all zeros (four 0x00000000)? It seems perfectly logical that a default constructor for any object would be the closest value that is conceptually "zero" for that class-type. Is allocating 128 bits of zero still too much overhead, or am I misunderstanding "uninitialized" in this context?
|
![]() |
|
VikingofRock posted:They are usually* different sizes and have slightly different semantics. Option<i32> is 5 bytes (probably +alignment), because it's got 4 bytes for the 32-bit integer plus an extra bit for the enum flag. MaybeUninit<i32> is 4 bytes, but the cost is that the user has to keep track of whether it's initialized or not on pain of undefined behavior. And one reason that this is important is that it means you can cast e.g. an array of 1000 maybe uninit<I32> into the array of 1000 i32s once you've initialised them but you cannot do that with options in general since the runtime representation is NOT the same. From the docs: code:
pseudorandom posted:I'm feeling the same way about this whole conversation. Why not just make the default constructor all zeros (four 0x00000000)? It seems perfectly logical that a default constructor for any object would be the closest value that is conceptually "zero" for that class-type. Is allocating 128 bits of zero still too much overhead, or am I misunderstanding "uninitialized" in this context? The latter, we were talking about guid collisions so whether an uninit guid is zero or really uninit I don't thunk makes much of a difference. If you use copy on write isn't allocating zeros even faster then actually doing lookups on whatever garbage location? Note that for, eg rust references where zero is not a valid value, mem::zeroed still causes ub without MaybeUninit gonadic io fucked around with this message at 07:55 on Jul 5, 2019 |
![]() |
|
pseudorandom posted:I'm feeling the same way about this whole conversation. Why not just make the default constructor all zeros (four 0x00000000)? It seems perfectly logical that a default constructor for any object would be the closest value that is conceptually "zero" for that class-type. Is allocating 128 bits of zero still too much overhead, or am I misunderstanding "uninitialized" in this context? uninitialized memory means undefined garbage. usually whatever junk was on the stack most recently. allowing access to uninitialized memory is a huge source of crashes and corruption and other demons of c and c++. so basically every language that isnt c or c++ ensures by various means that everything you can reference has been initialized. c# does it with structs by guaranteeing that at the very least its been initialized to 0. problem is that all 0s is most definitely not globally unique so having a guid type that does that by default is pretty sketchy
|
![]() |
|
Jabor posted:so how would you implement your list type? yeah making sure of that is the responsibility of the container implementation quote:in c# the language can't rely on you pinky promising not to do anything wrong, so what's your alternative? use extra space at runtime to track which slots in the array have been properly initialised? but c# does rely on your pinky promise of not doing anything wrong as shown by the uuid default ctor example at least in c++ the dangerous stuff is (by default) the responsibility of the container implementations of course people can go out of their way to use uninitialized memory too but at least you can construct your types in a way that prevent it
|
![]() |
|
Sapozhnik posted:ok now how do you raise an error wrap your result in a sum type (std::variant) together with the error(s) types you need
|
![]() |
|
Zlodo posted:yeah making sure of that is the responsibility of the container implementation it's a valid struct, in that all the fields are initialized and there aren't any pointers off into la-la land that are ripe for corrupting memory. your code is allowed to access all those default-initialized fields, and decide to do (or not do) things with them as it pleases. in c++, you literally pinky-promise to the compiler that you will never read from those uninitialized locations, and the compiler is allowed to mutate and optimize your code in all sorts of ways that will break horribly if it turns out that you were lying and do actually read those locations before you wrote something valid into them. Jabor fucked around with this message at 11:07 on Jul 5, 2019 |
![]() |
|
Jabor posted:Consider that the most common use case when allocating an array of guids (or any other struct) is that you'll replace everything that's initially in the array by copying other instances over the top of them. but why is that something you'd want to do in the first place? it feels like a weird consideration for something whose existence and use relies entirely on randomness like, have System.Guid() create a default v4 and have an alternative constructor that accepts null and spits out all zeroes or something or like System.NullGuid() or some poo poo or take it a step further and don't even have a guid type; just have a bunch of guid helper methods that spit out strings
|
![]() |
|
you really can't think of any reasons that someone might want to have an array, list, set, or map that contains guids?
|
![]() |
|
guids that aren't actually globally unique? no i really can't
|
![]() |
|
picture this scenario: you have a list of 64 guids. that list is currently backed by a 64-element array. you want to add a 65th guid to the list, which means you need to resize the array (and then copy everything from the old array to the new one). so, you allocate a new, bigger, 128-element backing array. does it make sense to generate 128 new random guids that will be overwritten by other things without ever being used?
|
![]() |
|
![]()
|
# ? Feb 9, 2025 14:25 |
|
i'd use List<T>.Add because we're talking about c#
|
![]() |