struct Strongbox
property opened = false
end
test = Hash(Int32, Strongbox).new
test[123] = Strongbox.new
if strongbox_variable = test[123]?
strongbox_variable.opened = true
end
# This is false
puts test[123].opened
# Second Test ----
test1 = Strongbox.new
test1.opened = true
puts test1.opened # true
When accessing the struct with an assigned local variable, the property is changed, but only inside that condition, not the struct itself.
What exactly is strongbox_variable? A local copy of the struct inside the conditional?
And if I were to use a class instead, it wouldn’t be a local copy, but a reference? Which makes the property become mutable?
If so, how come in the Second Test, the opened is mutable?
This leads me to believe… structs should never be used for mutable data, correct?
It does exactly what it should be doing. Since it’s a struct strongbox_variable is a copy of the one you set into test[123]. Thus, when you edit strongbox_variable, you’re not editing the one that is in the hash, just the local one called strongbox_variable.
Do know however that structs are mutable. It’s just that they are passed by value, not reference. See Structs - Crystal
Not really. All has to do with it being defined as a struct. strongbox_variable is being assigned a copy of whats in the hash. If it were a class, then it would be assigned a reference to the one in the hash, that would thus allow edits to be reflected in the original in the hash.
EDIT: But yes, structs are ideally used for immutable data.
if strongbox_variable = test[123]?
strongbox_variable.opened = true
end
Why would a developer need to modify a property that doesn’t actually get modified?
opened is set to true, but only inside that if condition block. That doesn’t make sense? If a developer is setting a property of a struct or class, the developer is inherently intending to modify that property? I’m so confused
struct Strongbox
property opened = false
end
test = Hash(Int32, Strongbox).new
test[123] = Strongbox.new
if strongbox_variable = test[123]?
strongbox_variable.opened = true
test[123] = strongbox_variable
end
puts test[123].opened #=> true
Using structs prevents things like that:
class Strongbox
property opened = false
end
test = Hash(Int32, Strongbox).new
box = Strongbox.new
test[123] = box
if strongbox_variable = test[123]?
strongbox_variable.opened = true
end
# This is true
puts test[123].opened
# This is printed
puts "Oops this box isn't supposed to be opened, only the one in the test Hash" if box.opened
Notice how when you pass the object to the method, obj is a copy of mut. Thus when you edit it, it changes the value on that copy, but the original value is still the same.
I always thought dot notation is tied directly to its parent object. I guess that’s not the case with “local copies of structs”. It’s tied to its parent object, but of that local copy, not the actual struct itself. If I got that right?
If true, I need to remove “dot notation is modifying its parent (a reference)” out of my head.
Or, leave it in my head but make a distinction it’s only for classes.
Dot notation just accesses/executes a given property/method on the given object. Doesn’t have anything to do with what obj its tied to. That would be more related to a struct vs class.
It’s a struct, so it gets passed by value. Thus you’re modifying a copy of that struct; which is the expected behavior since its a struct. If it were a class it would be passed by reference and modification would be reflected on the original object.
But this behavior is only happens when the object is passed. As you saw in your example when you were modifying the obj all in the same scope, your modification worked. But when it was passed to the method, it was no longer the “same” object, thus only affected that specific object.
Maybe looking it another way is better: don’t use struct unless they are immutable or unless you are willing to take copies, modify them and putting them back where they are stored, for performance reasons. If performance is not a problem when using a class instead of a struct then you should absolutely use a class.
Maybe something that the compiler could do is that there are changes in a value type and the value is never used again after the last change.
There is one “quirk” semantic in the language regarding value types that make things work when nested value types are used. I hope it won’t bring confusion or despair
The following works
struct Foo
property bar = Bar.new
end
struct Bar
property value = 0
end
foo = Foo.new
foo.bar.value # => 0
foo.bar.value = 1
foo.bar.value # => 1
Only if the Foo#bar is a single line body method. Because that let the compiler inline the getter, hence a copy of the inner value is avoided.
It’s safe to view them kinda like Tuples without dot notation? For example, my modify item tuple method returns a new tuple, but with certain values changed.
I got hung up on dot notation, kept thinking it was acting like a class. When it’s a local copy. However, in my mut.value = 2 example above, dot notation modification does work when you are accessing the “struct” itself, but not when accessing the local copy of the struct. Well, it does work… on that LOCAL copy, but not the main struct.
example:
if strongbox_variable = test[123]?
strongbox_variable.opened = true
# If this reassignment is not done, modifying "opened" is an illusion!
# test[123] = strongbox_variable
end
Knowing the differentiation between a local copy of a struct, and the struct itself is extremely beneficial. They can feel ambiguous sometimes. Especially when dot notation inherently implies the developer wants to change that property. When a developer is now modifying a property that is a local copy and not reassigning it…it now becomes an illusion to the developer and might create bugs?
Yeah, this is why I originally wanted to use a struct. Cause I have 2 properties (x and y). Which are immutable. And then I had 1 mutable property opened, and I remember people on gitter saying structs are mutable, so I thought it might be a good idea.
Quite frankly (not directed to you), I’m really tired of the “performance” mantra. I see it everywhere on gitter, reddit, etc.
For example, my JSON benchmark thread. If you use JSON.mapping with a struct it’s only negligibly faster. It’s actually 3 times more faster if you don’t call from_json, because the type checking slows it down significantly. But you have to use from_json. Which completely nullifies the entire benchmark. And also nullifies every point, every single user said that claimed “using a struct with JSON.mapping is faster!”
In fact, I might make an issue on GitHub about that. That’s not right, using a struct should be far faster. Hell, even @oprypin recommended and helped me significantly with a struct Message instead of using JSON.parse. from_json should not gimp performance that much, when the performance difference is over 3 times faster without it.
Because now, that just completely ruins the idea of a “statically typed” language. “Why create a struct when we can just do JSON.parse and use type checking?!”