New around here? Register your SA Forums Account here!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
cinci zoo sniper
Mar 14, 2013




c tp s: we tested in production, again. i love thing called "agile"

Adbot
ADBOT LOVES YOU

Jabor
Jul 16, 2010

#1 Loser at SpaceChem

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)?

animist
Aug 28, 2018
If you think about it, production is just a really big integration test

mystes
May 31, 2006

Jabor posted:

lol we just got done talking about that

but if you want to rehash it, you can start by telling us what you want to happen when someone makes an array of structs with a few thousand entries
Specify how to initialize it? I think Rust requires this, for example.

Zlodo
Nov 24, 2006

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)

fritz
Jul 26, 2003

animist posted:

If you think about it

why would i do a thing lik ethat

cinci zoo sniper
Mar 14, 2013




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

Fiedler
Jun 29, 2002

I, for one, welcome our new mouse overlords.

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

Xarn
Jun 26, 2015
C++ can do that because it allows uninitialized memory, which Java/C# promise to avoid. :shrug:

cinci zoo sniper
Mar 14, 2013




Fiedler posted:

there are way more testers hitting prod, though

exact same amount, actually. just with slight delay

Sapozhnik
Jan 2, 2005

Nap Ghost

Zlodo posted:

static factory methods are fine as long as you also make the ctor private

and obviously you dont write it like the horrible examples above

more like

auto pButt = Butt::Make(fart); where Make returns a unique_ptr or a shared_ptr

let's talk about mandatory default ctors on c# structs. Now that's garbage for idiots territory

ok now how do you raise an error

Jabor
Jul 16, 2010

#1 Loser at SpaceChem

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


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)

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?

gonadic io
Feb 16, 2011

>>=
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.

gonadic io
Feb 16, 2011

>>=
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

Fiedler
Jun 29, 2002

I, for one, welcome our new mouse overlords.

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

Xarn
Jun 26, 2015

Sapozhnik posted:

ok now how do you raise an error

MAGIC!

No, seriously, just return a nullptr.

Xarn
Jun 26, 2015
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)

Soricidus
Oct 20, 2010
freedom-hating statist shill
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.

gonadic io
Feb 16, 2011

>>=
in fact, MaybeUninit<T> entered stable rust today

Blinkz0rz
May 27, 2001

MY CONTEMPT FOR MY OWN EMPLOYEES IS ONLY MATCHED BY MY LOVE FOR TOM BRADY'S SWEATY MAGA BALLS
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

Beamed
Nov 26, 2010

Then you have a responsibility that no man has ever faced. You have your fear which could become reality, and you have Godzilla, which is reality.



When I saw that I immediately thought of your post. Finally, our code will be independent of initialization. :patriot:

Jabor
Jul 16, 2010

#1 Loser at SpaceChem

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#

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...

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.

Illusive Fuck Man
Jul 5, 2004
RIP John McCain feel better xoxo 💋 🙏
Taco Defender

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?

animist
Aug 28, 2018

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)

VikingofRock
Aug 24, 2008




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.

Nomnom Cookie
Aug 30, 2009



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

Lutha Mahtin
Oct 10, 2010

Your brokebrain sin is absolved...go and shitpost no more!

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

Arcsech
Aug 5, 2008

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

Sapozhnik
Jan 2, 2005

Nap Ghost
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

pseudorandom
Jun 16, 2010



Yam Slacker

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#

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...


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?

gonadic io
Feb 16, 2011

>>=

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.

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.

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:
let data = {
    // Create an uninitialized array of `MaybeUninit`. The `assume_init` is
    // safe because the type we are claiming to have initialized here is a
    // bunch of `MaybeUninit`s, which do not require initialization.
    let mut data: [MaybeUninit<Vec<u32>>; 1000] = unsafe {
        MaybeUninit::uninit().assume_init()
    };

    // Dropping a `MaybeUninit` does nothing, so if there is a panic during this loop,
    // we have a memory leak, but there is no memory safety issue.
    for elem in &mut data[..] {
        unsafe { ptr::write(elem.as_mut_ptr(), vec![42]); }
    }

    // Everything is initialized. Transmute the array to the
    // initialized type.
    unsafe { mem::transmute::<_, [Vec<u32>; 1000]>(data) }
};
Too bad this won't work for structures with more logic like hashmaps or ordered maps. Those you have to still insert one by one AFAIK.

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

Nomnom Cookie
Aug 30, 2009



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

Zlodo
Nov 24, 2006

Jabor posted:

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.

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

Zlodo
Nov 24, 2006

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

Jabor
Jul 16, 2010

#1 Loser at SpaceChem

Zlodo posted:

yeah making sure of that is the responsibility of the container implementation


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

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

Blinkz0rz
May 27, 2001

MY CONTEMPT FOR MY OWN EMPLOYEES IS ONLY MATCHED BY MY LOVE FOR TOM BRADY'S SWEATY MAGA BALLS

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.

Not only would creating hundreds of random guids be expensive, it would also be completely pointless because they'd never be used for anything.

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

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
you really can't think of any reasons that someone might want to have an array, list, set, or map that contains guids?

Blinkz0rz
May 27, 2001

MY CONTEMPT FOR MY OWN EMPLOYEES IS ONLY MATCHED BY MY LOVE FOR TOM BRADY'S SWEATY MAGA BALLS
guids that aren't actually globally unique?

no i really can't

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
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?

Adbot
ADBOT LOVES YOU

Blinkz0rz
May 27, 2001

MY CONTEMPT FOR MY OWN EMPLOYEES IS ONLY MATCHED BY MY LOVE FOR TOM BRADY'S SWEATY MAGA BALLS
i'd use List<T>.Add because we're talking about c#

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply