Why do I have to construct a new DataType every time I want to modify a single value?

Hello!

I’m not sure if this is the right section, please let me know if it isn’t and I will move it.

Anyway, I just don’t understand why modifying a single value doesn’t work when trying to modify a single value inside of a data type. I have been working on solo projects for probably over a year and I just can’t seem to wrap my head around why I am not able to do it and was hoping someone could help me understand why it doesn’t work.

For example,

local part = Instance.new('Part')
part.Parent = workspace
part.Size.X = 10 -- errors

However, if I do

part.Size = Vector3.new(10, part.Size.Y, part.Size.Z)

it doesn’t error

The same thing seems to happen in Visual Basic which I used in Computer Science quite awhile ago

textBox.Font = New Font("Times New Roman") ' This works
textBox.Font = "Times New Roman" ' This doesn't work and would cause the program to crash

But the weirdest part which I just discovered is using raycasting, you seem to be able to do this, defying the above logic? In fact, you aren’t able to modify any properties variables thanks @plasma_node inside of the parentheses, it has to be outside of them.

local raycastParams = RaycastParams.new()
raycastParams.IgnoreWater = true

Using the logic above, it should error, right? But it doesn’t.

4 Likes

This is not really a question, like “why?” It just happends I guess, though I do agree, Vector3’s should be able to be modified like that!

1 Like

I like this question, I’ve wondered this aswell and also like seeing more unique questions in this category for once.

For one, when you’re modifying a raycast params object that’s a table/dictionary/constructor or whatever and not an instance, and thus the required method of property assignment could differ.

Take this with a big grain of salt since this is just speculation on my end:

It’s possible this is how the engine is built. When you do Object.Position.X you’re reading data and can’t assign. So potentially when you do object.Position = somePosition you’re setting a value but when you do object.Position.X you’re effectively reading a new variable instead of a pointer that references the value of the property.

Might also be a deliberate security setting, in such case not sure why but it would be interesting to see what would happen if someone tried to force Roblox to do that on an elevated command line.

Edit: I believe this is called “Immutability”.

In which case, it’s possible it’s threading related.

2 Likes

This happens because those properties use different data types. A simple way to imagine data types is that they’re basic objects representing a value. For instance, an integer data type could hold the number “1.”

The reason you can’t assign only the X of a Vector3 programmatically is because a Vector3 is a Vector3 data type. You can’t make a Vector3 equal 10— then it would be an integer.

VB is the same thing. Fonts are not strings. They are an object, so they must be created.

RaycastParams is an object too. It has properties like any other object so it makes sense to modify it the way we do.

3 Likes

But in the case of the vector3 question, the type of Part.Position.X is number and so is Vector3.X so that doesn’t make sense as the explanation for why Part.Position.X = 1 is not allowed.

Also
@LucasTutoriaisSaimo
@ItzMeZeus_IGotHacked

Not trying to minimod but there’s really no point to debate where this thread should be as it’s off topic and beside the point.

1 Like

It’s a good question, however, I believe you can just do like:
Instead of this:

Just do:
part.Size += Vector3.new(10,0,0)

You can’t modify a data type like that. You have to create a new Vector3. Vector3.X, Y, and Z just represent that part of a Vector3, which is why you can read them, but not write them.

1 Like

How about:

print(typeof(part.Size.X))
1 Like

Yes we know, the question was why, what at a technical level is preventing that.

So there has to be an explanation other than “you just can’t”

My theory is that when you read Part.Position.X you’re getting a raw value and when you read Part.Position you’re getting a reference to the original property.

The question is why, not can.

Edit: Oh joy. 18 replies and half are off topic.

2 Likes

I think really it’s more like, yeah why doesn’t roblox allow that? I don’t think it would be that hard to implement, certainly could make code smaller;

Using Position.X reads the Vector3 and passes the X argument only; You can’t do much;

@plasma_node & @Intended_Pun

Got it for the raycast thing haha.

Ah okay, so it’s similar to how this stores the string itself and not the field in which the value is stored?

local text = textObject.Text

That sort of makes more sense. But if it has properties within the instance, I don’t really get why it can’t be modified similar to a normal property.

Another thing is that this will error even though it isn’t actually applied to an instance, maybe this is an irrelevant acknowledgement though haha.

local v3 = Vector3.new(10,0,0)
v3.X = 5

Also as @ItzMeZeus_IGotHacked said, when using the type() function on it, it still returns an integer.

Also, if you run Vector3.new().X = 1, it will say “X cannot be assigned to”. So that leads me to think maybe it is more like a string than an instance on the backend? Or maybe as you said, it’s more of a security thing which would make sense I think.

I think Unity supports Vector3 as well but I have no experience with that, maybe if someone does they could check?

1 Like

My guess is they do it this way (on Roblox) because you don’t need to worry about the semantics of mutating data types. For example:

local x = Vector3.new()

part.Position = x
x.Y = 10

It’s not well defined what should happen here. Most data types are stored on the heap, so technically this should also refer to the same vector as the position and change it.

However this would create an issue with future optimizations or behaviors, like how Vector3 won’t be or is no longer stored on the heap. It would break the behavior.

9 Likes

Ah yeah, optimizations makes sense.

I’m a little confused by your code example though. When you’re applying a vector3 to a part’s position, wouldn’t that just sort of “clone” or take the information from the v3 and then just apply to the part, then when you do x.Y = 10 that would just modify the already existing variable? I’m not too sure how Lua works on the backend though, maybe I’m just not making sense.

Types like Vector3 are immutable because they are read by value and not by reference. The system that bridges the information between Lua and C++ isn’t concise enough to give you direct memory access to the fields, so they bridge it across such that there’s no disambiguation to how the value is bridged into Lua. It’s not a pointer, nor a reference, it’s a copied value because that’s just how type safety in Lua’s C API rolls.

If the type were mutable, a newer developer might get confused and think that if you fetch the position of a part, it’s an actual reference to the value of the property and setting something like Vector3.X will automatically reflect to the value of the property. And again, that isn’t the case here.

Now that’s not to say it should be impossible to write to these fields. It would perhaps be faster than having to constantly allocate a new immutable for each operator output, but these decisions were made extremely early (i.e. 2004-2006) and have stuck since.

(Looks like @Autterfly summarized this better than me, oh well lol)

8 Likes

The only reason why Part.Size.X and other such direct chunks of a Vector3 or UDim values exists is to be a read only state to easily pull data from an object in the case of you wanting to either manipulate that object using the previously created data or create a new object with the same data but let’s say maybe only one of the 3 vector value pieces.

I used to always be confused by the read only sections of various objects but what you’ll find is that when you work with more complex things, those little shortcuts can hinder you down the line.

1 Like

Adding onto this regarding RaycastParams though, it was a surprise for me as well to see a mutable type introduced, but it’s actually always been possible.

I got to see Roblox’s C++ backend when I was an intern, and all of the datatypes actually have to have a setter implementation. Most of them just ignore attempts to set a value and throw an error back.

There seems to be a good rationale for types like RaycastParams and CatalogParams to be mutable though:

  • It’s intended for function parameters and isn’t reflective of any object’s properties.
  • There’s a lot of fields, and the potential for more to be added, so it makes sense to have default fields that can be changed on a case-by-case basis without having to fill all of them out as you would have to do in the original raycasting API.
  • Once the values are set, they’re already bridged back to the allocated params in C++ so it’s faster to reuse it

Bridging the Vector3 position of a part in real-time would probably be expensive on performance. Not to mention that all Lua numbers are 64-bit doubles while Roblox uses 32-bit floats for Vector3.

Frankly more datatypes like these should have setters, such as DockWidgetPluginGuiInfo and TweenInfo to name a few.

5 Likes

Ah okay, that does make sense.

Just out of curiosity, since Roblox uses Luau, do you think they would (or are capable of) ever implementing Vector3 values whose properties can be edited?

It has always been possible, they just chose not to do so.

1 Like