Why should you use string.format as much as possible rather than concatenation?

print("Str1 " .. "Str2") -- Str1 Str2

VS

print(string.format("Str1 %s", "Str2")) -- Str1 Str2

Why is string.format considered a better practice? Does it simply look better in Lua code convention? Easier to localize? Is it faster? I’ve had several people tell me to use string.format more than concatenation, but I don’t know why is that the case.

7 Likes

In terms of performance, the difference is negligible and it can be considered a microoptimisation. string.format is typically preferable over concatenation because of the way it looks in your code (much cleaner). string.format is also free of data type binding, while for concatenation you need to do type casting.

local ERROR_STRING = "bad %s to %d"
print(string.format(ERROR_STRING, "argument", 2)) --> "bad argument to method"

-- vs.
local ERROR_TYPE = "argument"
local ERROR_CODE = 2

print("bad" .. ERROR_TYPE .. "to" .. tostring(ERROR_CODE)) --> "bad argument to 2"
-- ^ You could just do print("bad", ERROR_TYPE, "to", ERROR_CODE) though, but why should you if you don't need to?

The print example above isn’t as good an example since print accepts varargs and coerces all arguments to a string format (iirc). A better example would be constructing a raw string.

local A = 2
local B = "bad argument to" .. tostring(ERROR_TYPE)
6 Likes

You can use it for more data types than strings essentially.
If you look in the lua reference manual for string.format, it says it follows the same kinds of format specifiers as the printf function in C, which can be found here, List of all format specifiers in C programming - Codeforwin, or just by googling “c format specifiers”.

The full definition of string.format from the lua manual:

1 Like

Concatenation is actually quite a bit more efficient, but you should use whatever you think is more intuitive. I personally use string.format, then rely on my source simplifier to convert it to the concatenation form.

1 Like

I’m pretty sure “%s” only works for strings and numbers (same as concat)

1 Like

What do you mean by this, like none of the other format specifiers work?

I meant that I’m pretty sure these would error in roblox’s version of lua:

string.format("%s", {})
string.format("%s", true)

I’m on mobile and can’t double check

1 Like

Honestly, I didn’t even expect numbers to be accepted for “%s”, but I guess it’s good to know that no other one would work either…

Edit: I tested your theory out for each data type since you were on mobile, and this is what I got:

local function tryprint(data)
	local s = pcall(function()
		print(string.format("%s",data))
	end)

	if not s then
		print("errored for " .. tostring(data) .. "!")
	end
end

local datatypes = {
	"string",
	0,
	false,
	function() end,
	coroutine.create(function() end),
	Vector3
}

for _,data in ipairs(datatypes) do
	tryprint(data)
end

image
You were correct!

2 Likes

Numbers and strings are surprisingly interchangeable in Lua.

Concatenation will always be a deals faster; I wouldn’t say it’s too negligible of an optimization. Lua has a built in instruction for concat by .., whereas format has to go through a longer process.

5 Likes

I agree with you, the other way around look contradicted. Because string.format is also a lua function which just eases string manipulation. i guess it uses concatenation internally anyway. Unless if it’s a C function and there is some work around.
Someone to correct me if i’m wrong.

Sorry for the bump but I really have no idea which one is the correct answer here. The answer could prove useful to those of others that search this exact topic.

Concatenation should be used when you find it easy. Overall, concatenation is more efficient as opposed to string.format, but the difference is minuscule. For a string that requires a lot of concatenation, it may be better to use string.format, as your code will look neater. Basically, use concatenation whenever you can, but if you find you are using concatenation on a single string like 5 times (like formatting an error message), it will look cleaner to use string.format.

1 Like

My response is based on very shallow testing on the engine so I wouldn’t take my word for it, consider trusting Autterfly’s more as they are more well-versed with how Lua is implemented. I typically go for the one that best suits my use case and looks cleaner in my code.

The performance difference really isn’t worth picking at but you also want to consider your circumstances such as how much of it you’re doing and how time critical your code is.

2 Likes

The purpose of string.format() is simply the name of the function: to format strings. Take, as an example, the following table of numbers:

local nums = {
    [1] = 0.6856770184,
    [2] = 0.2539193560,
    [3] = 0.8754895076,
    [10] = 0.7080842266,
    [250] = 0.8815417651
}

If we want to print this table in the format of "index = value\n", then here is one way to hardcode a print statement with concatenation for this table:

print(
       "1 = " .. nums[1]
    .. "\n2 = " .. nums[2]
    .. "\n3 = " .. nums[3]
    .. "\n10 = " .. nums[10]
    .. "\n250 = " .. nums[250] .. "\n"
)

This will then print out the following:

1 = 0.6856770184
2 = 0.253919356
3 = 0.8754895076
10 = 0.7080842266
250 = 0.8815417651

Notice that the = signs do not line up. In this example, it may not seem like a large issue. But if this table were to consist of many more elements, then the lack of alignment would not look pretty and make reading the output quite difficult. The cause of this misalignment is the width of each index’s number. 1 has a width of 1 character, along with indices 2 and 3. However, 10 has 2 characters, and 250 has 3. Wider numbers push the equal signs further out of alignment. Besides this lack of alignment, there is yet another issue: the values. Oftentimes, you do not need to see the entire decimal value–only, say, three decimal places are all you need. Now, let’s try using string.format along with some flags to print the output. Our code will be the following:

print(string.format(
    "%3d = %.3f\n%3d = %.3f\n%3d = %.3f\n%3d = %.3f\n%3d = %.3f\n",
    1, nums[1], 2, nums[2], 3, nums[3], 10, nums[10], 250, nums[250]
))

And the output is:

  1 = 0.686
  2 = 0.254
  3 = 0.875
 10 = 0.708
250 = 0.882

It is now extremely easy to see which side represents the index and which side represents the value. The decimal values are also printed to 3 decimal places, which is what we wanted. By definition, we have formatted our output to be such that indexes, printed on the left, are followed by an equals, where all equals on every line are in line with one another, followed by decimal values printed to the third decimal place. This is what string.format() enables programmers to do, and is why it is an extremely important function. Notice also that it is very easy to see what the output should look like from the code. We see that the string to format consists of "%d = %f\n" repeated many times over, which reveals to the programmer that the output is supposed to be “number = float\n”.

Something to note is that string formatting can be used for more than just console output. For example, if, in a game, you had to show the message "Player PlayerName spent $100 and now owns the AwesomeItem!" then in your code you could have this:

local gameMessage = string.format("Player %s spent $%d and now owns the %s!", playerName, amntSpent, itemName)"

instead of this:

local gameMessage = "Player " .. playerName.Name .. " spent $" .. amntSpent .. " and now owns the " .. awesomeItem.Name .. "!"

In the example with formatting, it is extremely easy to identify what the text is supposed to be. It is also extremely easy to modify the message to display something different. Contrast this to the concatenated version. Depending on what you want to change, you may have to shuffle concatenated values around, split string segments to fit in new values, or merge string segments. In format, all you’d have to do is move your %s around or put in a new %x in there.

All in all, the final answer to your question is that the formatting capabilities string.format() provides (that are not present with basic concatenation) are extremely powerful and useful, and it is because of this that you should use string.format() often. Unlike what many of the responses in this thread discuss, the use of string.format() is not based on efficiency; it is instead based on its utility. Of course, this is not to say that string.format() will always be better than concatenation–in the case of the examples provided in the original post, concatenation is far, far simpler to type. But for more complex and important strings, string.format() will be a heavily useful tool.

If you are curious about the weird %3d and %.3f things in my example with the nums table above, then you should look up string format specifiers in Lua (or c, as Lua’s string specifiers are the same as c’s). The character % is an escape character for the format type, the character d is the replacement value’s data type (d is for digit (integer), f is for float (decimals)), and 3 and .3 are optional flags that provide further formatting information.

4 Likes