Using Vector3 as key in a dictionary?

This works fine in C#, but prints nil in Lua. Is there no way to use a Vector3 as a key?

local Dictionary = {}
Dictionary[Vector3.new(0,0,0)] = Vector3.new()
print(Dictionary[Vector3.new(0,0,0)])
1 Like

I don’t think Vector3s are valid keys. You can probably only use string and numerical keys.

1 Like

They are valid keys. The issue is simply the fact that each Vector3 is its own object.

7 Likes

Any non-nil value in Lua is a valid key in a table.

2 Likes

It’s because your using Vector3.new
This following statement would return false because they’re different objects, even if they have the same values

print(rawequal(Vector3.new(0, 0, 0), Vector3.new(0, 0, 0)))
7 Likes

It’s because he’s using a seperate vector object as the key. The value doesnt matter, the index does.

2 Likes

Clarified what I’m trying to explain in my edit.

Yeah. As soon as I replied I saw your edit haha.

How would I make this work if each Vector3 is unique?

nils, booleans, numbers, and strings in Lua are handled by value. They are immutable, meaning they cannot change.

functions, threads, tables, and userdata (the vanilla type of a Vector3 and all other custom types in the Roblox API) are handled by reference. They are mutable, meaning they can change.

What does this mean? Consider:

local t = {}
local part1 = Instance.new("Part")
local part2 = Instance.new("Part")
t[part1] = "foo"

print(t[part2]) --> nil

The parts are different Instances (userdata under the hood) so act as a separate key—it makes sense to think of them this way. The same applies to every other above data type that I mentioned as being handled by reference. You’re trying to get the value of a seperate Vector3 compared to what you initially used as the key. The following code would be a working replacement:

local vector = Vector3.new()
local t = {}
t[vector] = "foo"
print(t[vector]) --> foo
4 Likes

Maybe convert the vector3 to a string?

2 Likes

print(rawequal(tostring(Vector3.new()), tostring(Vector3.new()))) --> true :slight_smile:

1 Like

Similar happens with signals, I noticed this bug when I was trying to create a signal wrapper module:

local t = {}
t[game:GetService("ScriptContext").Error] = "test"
print(t[game:GetService("ScriptContext").Error]) --> nil

Roblox solves this for Instances by having a cache of instances that they return, but does not have this for any other object type.

Functions are not mutable by Lua definition. They are passed by reference but you can’t mutate their contents in the context of Roblox.

1 Like

To solve the issue in the thread, you can convert the key into a string. The reason why your original script doesn’t work is because each Vector3 is treated as a different object, even if they contain the same values.

Here is an example of the fixed script:

local Dictionary = {}
Dictionary[tostring(Vector3.new(0,0,0))] = Vector3.new()
print(Dictionary[tostring(Vector3.new(0,0,0))])
2 Likes

You can:

local function f() end
setfenv(f, {})

You can also assign the metatable of functions and change their upvalues using the debug library, which Roblox restricts.

That is not editing the function. That is editing variables the function uses. Upvalues are local variables the function doesn’t own, the environment is a table variable the function uses.

Side note you can assign the metatable of any data type through the official debug library.

1 Like

The environment and upvalues of a function are properties of that object that fundamentally change its behaviour. If an object’s properties can be changed, I’d consider that object to be changeable/mutable.

In the end, this comes down to semantics.

I’ve just tested this:

local function f1()
    
end

local function f2()
    
end

debug.setmetatable(f1, {})
debug.setmetatable(f2, {})

print(getmetatable(f1) == getmetatable(f2)) --> true

I was wrong about functions having their own specific metatables like tables and userdata. :+1:

It’s not exactly down to semantics. Functions are not mutable. To be specific with the terminology, it’s probably more appropriate to differentiate functions from function instances.

Functions are blueprints, which are immutable, and don’t hold an environment or upvalues.

Function instances are “mutable” in the sense they hold references to these which can change, but the actual code (function “blueprint” independent from the instance) doesn’t change.

I hope this makes what I meant clearer.

3 Likes

Thanks for the explanation—very helpful.