262

I am confused about the difference between function calls via . and via :

local x = {
    foo = function(a, b) return a end,
    bar = function(a,b) return b end
}

return x.foo(3, 4) -- 3
return x.bar(3, 4) -- 4
return x:foo(3, 4) -- table: 0x10a120
return x:bar(3, 4) -- 3

What is the : doing?

1

4 Answers 4

374

The colon is for implementing methods that pass self as the first parameter. So x:bar(3,4)should be the same as x.bar(x,3,4).

Sign up to request clarification or add additional context in comments.

10 Comments

ah... so it's object-oriented syntactic sugar.
Exactly. In the entire reference manual, the only blurb they give on this is "The colon syntax is used for defining methods, that is, functions that have an implicit extra parameter self." (5.0 manual, bottom of pdf page 19)
ooh ahh... I was going to ask where the official docs were on this, but you beat me to it. nicely done. :-)
@keyle It depends on the self object will go as the first parameter and its properties value.
@keyle Colon syntax would be a little faster if the object you're calling is not a local, since the virtual machine retrieves it only once. Basically dot syntax like object.method(object,args) retrieves object twice, while object:method(arg) retrieves object only once. If object is a global, upvalue or table field, then : is faster than .. . is never faster than :.
|
51

For definition it is exactly the same as specifying self manually - it will even produce same bytecode on compilation. I.e. function object:method(arg1, arg2) is same as function object.method(self, arg1, arg2).

On use : is almost the same as . - a special kind of call will be used internally to make sure object and any possible side-effects of calculations/access are calculated only once. Calling object:method(arg1, arg2) is otherwise same as object.method(object, arg1, arg2).

Comments

42

To be completely precise, obj:method(1, 2, 3) is the same as

do
  local _obj = obj
  _obj.method(_obj, 1, 2, 3)
end

Why the local variable? Because, as many have pointed out, obj:method() only indexes _ENV once to get obj. This normally just important when considering speed, but consider this situation:

local tab do
  local obj_local = { method = function(self, n) print n end }
  tab = setmetatable({}, {__index = function(idx)
    print "Accessing "..idx
    if idx=="obj" then return obj_local end
  end})
end
tab.obj.method(tab.obj, 20)
--> Accessing obj
--> Accessing obj
--> 20
tab.obj:method(10)
--> Accessing obj
--> 10

Now imagine the __index metamethod did more than just printing something. Imagine it increased a counter, logged something to a file or deleted a random user from your database. There's a big difference between doing that twice or only once. In this case, there's a clear difference between obj.method(obj, etc) and obj:method(etc).

4 Comments

You really shouldn't worry about such stuff. If you have to, there is something just terribly wrong with your architecture.
I'd say it's the other way around; good code should not make any assumptions about implementation details of unrelated code. Function calls may or may not be memoized, that doesn't mean it's good practice to call them more often than needed.
While I see it is definitely good to know, if you are doing this sort of thing you already know what will be happening. If you're planning on 'deleting a random user from your database' when the property is accessed, you might want that to occur both for the method access and for the parameter.
The example wasn't really meant to be realistic; the point is simply that side-effects will only happen once with :, which is the reasonable thing to happen, so expanding it without a variable will change the behaviour in very subtle ways that won't break things in 99% of cases (but those bugs are often the hardest to find)
2

It's syntactic sugar:

  • When defining a function, it adds implies a 'self' argument passed to the function
  • When calling a function, it inserts the left of the colon as the first parameter.

Here shows defining the function each way, and calling each of those both ways:

obj = {name = "alice"}
function obj.Greet(self, greeting)
  print(greeting..", "..self.name)
end
obj.Greet(obj, "hello")
obj:Greet("hello")

function obj:Thank(message) -- implied 'self' parameter
  print(message..", "..self.name)
end
obj.Thank(obj, "thank you")
obj:Thank("thank you")

Online Example

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.