Newproxy, what is it and how can I utilize it?

Introduction

Hello I’m dand and I’ve been scripting for over a year now and I’ve come across newproxy, a Lua Global that may be confusing to some especially new programmers.

Short answer

Well its a lua global that can be called to create a blank userdata.

In short its explained in the dev hub as ‘Creates a blank userdata , with the option for it to have a metatable.’

Lua Globals

What is userdata?

Basically since lua is an extension language it offers a custom datatype (e.g strings, numbers, booleans) userdata for custom behaviour.

In roblox userdata is used in Instances to give custom behaviour. You can test this by running;

print(type(workspace)) -- userdata

To give userdata its functionality you can offer to attach an empty metatable by puting true as the first parameter to it which you can later edit by using getmetatable


Example

Now lets utilize userdata with Newproxy

local userdata = newproxy(true) -- returns a blank userdata with a blank metatable.
local meta = getmetatable(userdata)

print(meta) -- {}

Giving functionality!

Now how will we give userdata functionality? Its basically just an empty peice of data.

Adding metatables!

Lets give this userdata a name.

local userdata = newproxy(true)
local meta = getmetatable(userdata)

print(userdata) -- userdata 0xsomething211f228

meta.__tostring = function()
  return 'name'
end

print(userdata) -- name

Giving values

Now what if you wanted to change that name or maybe make a custom Instance?

Lets create a new dictionary with all internal data!

local userdata = newproxy(true)
local meta = getmetatable(userdata)

local internal = {}

internal.Name = 'something' -- Add a value to the internal dictionary

meta.__tostring = function(t)
  return t.Name
end

meta.__index = internal

print(userdata) -- name
print(userdata.Name) -- name

userdata.Name = 'something else' -- errors

Changing values

But what if we want to change the name?

Well just add a __newindex metamethod!

local userdata = newproxy(true)
local meta = getmetatable(userdata)

local internal = {}

internal.Name = 'something' -- Add a value to the internal dictionary

meta.__tostring = function(t)
  return t.Name
end

meta.__index = internal

meta.__newindex = internal

print(userdata.Name) -- something

userdata.Name = 'something else'

print(userdata.Name) -- something else

Conclusion

Now why would you use userdata? It just seems like a complex and harder table with fancy words.

Well in the grand scheme of things, its pretty useless unless you’re trying to make something very complex which requires such things as change tracking etc. But to me its still fun to know.

This was a bit of a basic guide and only included what I fully understood, click this topic for more in depth explanation.

Fell free to comment if I got anything wrong or if I missed something.

Thanks for reading,
Dandcx

Simple Custom Instance with newproxy

I was bored so I decided to try and replicate a custom instance (since this is probably the closest you’ll get)

local function CreateProperty(container, name, valuetype, value, locked)
  local property = {}
  property.Name = name
  property.ValueType = valuetype
  property.Value = value
  property.Locked = locked or false

  container[name] = property

  return property
end

local function CreateCustomInstance(name, classname, properties)
  local userdata = newproxy(true)
  local meta = getmetatable(userdata)
  local internal = {}
  
  CreateProperty(internal, 'Name', 'string', name, false)
  CreateProperty(internal, 'ClassName', 'string', classname, true)
  CreateProperty(internal, 'Parent', 'userdata', nil, false)
  CreateProperty(internal, 'Archivable', 'boolean', true, false)

  for i,v in pairs(properties) do
    CreateProperty(internal, i, type(v), v, false)
  end

  meta.__tostring = function(self)
    return self.Name
  end
  meta.__index = function(self, i)
    local property = internal[i]
    if property then
      return property.Value
    end
  end
  meta.__newindex = function(self, i, v)
    local property = internal[i]
    if property and not property.Locked and type(v) == property.ValueType then
      property.Value = v
    end
  end
  
  
  return userdata
end

local custom = CreateCustomInstance('a thing', 'CustomInstance', {
  SomeProperty = true,
  AnotherProperty = 'aaaaa'
})

print(custom.Name) -- a thing
15 Likes

This is a good brief overview, and I am surprised that despite you only scripting for a bit over a year as you mentioned you are at this point so I commend you for that.

I would just like to note that as you said newproxy and blank userdatas have very little practical applications, and only necessary when altering the functionality of something is a priority which can almost always be worked around. Personally i’ve never found it useful.

However despite that being said, good job with this article. :slight_smile:

3 Likes

Thanks, I just wanted to help clearup any confusion for new scripters about userdata since when I was first learning it I found it quite hard to get the hang of.

Not sure what exact version of Lua Roblox currently runs, but using newproxy to create blank userdata has become redundant and somewhat deprecated since Lua 5.2. I’m assuming that because it still exists Roblox runs near Lua 5.1

Because setmetatable, rawset, rawget are only supported by table types, newproxy as the name suggests acts as a perfect proxy for indexing.

Provided Roblox is on Lua 5.1, it means that __len is ignored for tables (it only works on userdata). So in very specific situations, newproxy can be used for this. (__len is a vanilla metamethod, that manipulates # operator on tables, similar to metatable mathematic operators).

In general you probably won’t see newproxy being used. It builds habits that work in an oudated version of Lua on a platform that is subject to deprecate it at any time. It doesn’t mean they will, but you should only use it if you have to. Perfect to use as a proxy if you’d like to simulate something like a read-only table, but you could create a proxy table to do that anyways.

In almost every single situation, you should be using setmetatable({}, {}) before using newproxy. It can be used for security surrounding modules, but probably shouldn’t be. It’s worth understanding, but likely not a habit you should build, same as setfenv(), getfenv(), and _G.

2 Likes

Just because it is ignored for tables that does not imply it only works on userdata.

__len works on every value expect tables and strings in Lua 5.1, not just “only works on userdata”.
If you look at the source code of Lua 5.1, you’ll see this.

https://www.lua.org/source/5.1/lvm.c.html

      case OP_LEN: {
        const TValue *rb = RB(i);
        switch (ttype(rb)) {
          case LUA_TTABLE: {
            setnvalue(ra, cast_num(luaH_getn(hvalue(rb))));
            break;
          }
          case LUA_TSTRING: {
            setnvalue(ra, cast_num(tsvalue(rb)->len));
            break;
          }
          default: {  /* try metamethod */
            Protect(
              if (!call_binTM(L, rb, luaO_nilobject, ra, TM_LEN))
                luaG_typeerror(L, rb, "get length of");
            )
          }
        }
        continue;
      }

Roblox is running Luau which is based on Lua 5.1.

I’m not sure how newproxy can be deprecated when it was never documented in the first place.

It was removed in Lua 5.2, not “somewhat deprecated” either way.

4 Likes