Is it possible to never return a function?

Hello. I would like to know if there’s a way to never return a function so that the code that called it doesn’t continue. Traditionally, you would return from the place that handles the returned value but I would like to do this from the function instead so that I don’t have to repeat the same return line…

if value == nil then return end

…every time the function is used. Complete annotated sample:

local function someFunction()
  local valueToReturn = {}

  if false == false then
    valueToReturn = nil
  end

  return valueToReturn -- nil will be returned, but...
  -- nil will cause an error in the code below if not stopped. therefore...
  -- I want this function to stop and never return
end

local value = someFunction()
if value == nil then return end -- traditionally, this is how a bad value is stopped. but it is verbose to write this every time we use the function. is there a way to do this in the function itself?

value.property = true -- if not stopped, an error will occur because the value is nil
local function someFunction()
  return nil
  -- want this to never return
end

local value = someFunction()
if value then 
   value.property = true -- just do the opposite 
end 

That is what I am doing at the moment.
But if I do this several times…

local value = someFunction()
if value then 
   value.property = true
end 

local value = someFunction()
if value then 
   value.property = true
end 

local value = someFunction()
if value then 
   value.property = true
end 

local value = someFunction()
if value then 
   value.property = true
end 

local value = someFunction()
if value then 
   value.property = true
end

See how I have to state

if value then
...
end

every time? I want to do this from the function instead.

What’s your use case for this? Perhaps there’s some better way to do this.

However, for your example you could do something like instead of returning the value then setting it, you just set it in the function. This would save you from having to type it out every time after the function.

For example:

local function setValue()
   if someValue then
       value.Property = true
   end
end

Don’t mind the changing of the property, that is irrelevant and just there to show that the code must be stopped otherwise it will error.

It’s for any use case where a function that can potentially return a nil value is being called several times in a place that will error if not stopped. The way I’m doing it at the moment works but I’m wondering if there’s a more efficient way of doing it so that I don’t have to state

if value == nil then return end

every time in the origin code, and rather only do this check in the function itself and never return if it is nil:

local function someFunction()
  local valueToReturn = 5
  if false == false then
    valueToReturn = nil
  end
  -- the value is now nil

  if valueToReturn == nil then
    -- never return!!
  else
    return valueToReturn
  end
end

Functions can’t be cancelled when called unless you write the boilerplate to simulate a cancellation by preventing subsequent code from running. Same goes with preventing functions from returning (which, if a function finishes, it will always return something or void).

There’s no easy-way-out like what you’re looking for in the thread without writing the verbosity or framework to handle that. Depending on your circumstances though, you could get close enough to this by incorporating Promises into your code.

Promises are cancellable, though in the context of Promises cancellations are applicable to Promise chains and not the functions themselves, that’d still be on you to write code for. You’d mainly be looking to create chains to handle your processes then. For example, your existing code could be translated as:

Promise.new(function (resolve, reject, onCancel)
    -- Do something

    if onCancel(function ()
        -- Handle cancellation
    end) then return end -- Subsequent code doesn't execute

    resolve(true)
end):andThen(function (resolutionValue)
    value.property = resolutionValue
end)

Disclaiming that I don’t cancel my Promises right now so I’m not well-versed in deploying cancellation handlers. Best read the documentation and do some experimenting if you’re interested.

1 Like

you could error, that will cancel up until you have a pcall or thread, I think more context is needed though as this sounds very silly to not handle potential nil values

Thank you, this is exactly the confirmation I was looking for. I didn’t believe it was possible but I thought I’d ask in case there was a better way I could be doing this. Really sucks that this can’t be done in a convenient way, it’s really annoying to have to state the same thing over and over when it could obviously be simplified.

Yeah, I guess erroring is another way but that’s a bit messy. Like colbert said, I don’t think there’s any real nice way of doing it.
Nil values would be handled in the function.

Call coroutine.yield() where you want the code to end. It does exactly what you asked for. The function will never return, it will never finish.
It’s exactly like wait() except instead of continuing after some time, it just stops and never starts again.
It’s literally “stop here and wait to be resumed” except you haven’t told anything to resume the thread so it never gets resumed.

I really recommend that you throw an error and handle it at a favorable point, however.

Wrap the entire thing you’re doing in a pcall and simply let the error happen.
Either do something like assert(valueToReturn) or if not valueToReturn then error() end in the function, or just calmly return nil and cause an error elsewhere.

local function doTheThing()
	someFunction().property = true
	someFunction().property = true
	someFunction().property = true
end

pcall(doTheThing)

-- or simply
pcall(function()
	someFunction().property = true
end) -- etc.

It will not make an error message because it has been caught and ignored.
The downside is an extra level of indentation and a wrapping, as it seems you’re doing this on the left edge of a script and not in a function of some sort.

Ahh yes, I never thought about using coroutine. Thanks for recommending that. Do you know if there are any performance implications to using coroutine.yield(); does it act as an infinite wait or does it actually stop?

Just got a chance to try coroutine.yield in this context - works like a charm. Making your answer the solution.

Can you explain why you can’t just use a while loop?

while true do
    local value = someFunction()
    if value then
        value.property = true
        task.wait()
    else
        break
    end
end

@OperatorsManual

If I were to take the coroutine option (which I wouldn’t and I’ll just handle the error like @Eestlane771 said), I wouldn’t just coroutine.yield() and leave there, while that thread won’t be using any processing power, it will still occupy space in the memory, so what I would instead do is call task.cancel(thread) to kill the thread and have no fear for memory leaks.

And just for the sake of clarity, it’s not exactly like wait(), the only thing they share in common is yielding and that’s about it.

A while loop would be very expensive and I’m not really sure how it would help anyway since breaking would just cause the function to continue.

Yes, memory was something I was concerned about. I’d rather not use the pcall method because it’s messier and occupies more lines than necessary.

I’m actually not really sure how the coroutine method is even working. I didn’t even have to wrap the function in coroutine.wrap and it’s just… working (no errors, either). So I’m not really sure what I would pass for the thread argument in task.cancel.

Edit: Never mind, I did task.cancel(coroutine.yield()) and that seems to have worked.

That should not work, coroutine.yield() does not return you the thread, so I really don’t know what you are canceling with that, you need to do something like this:

local thread = coroutine.create(yourFunction())
coroutine.resume(thread) -- start the thread because coroutine.create doesn't run it for you

-- Whenever you want to cancel it, do this
task.cancel(thread)

I’m confused as to why it works too, but it does, and it’s not producing any errors… here are the functions I’m applying this to:

GetPlayerInventory module

local PlayerInventories = require(game.ServerScriptService.Shop.SessionData.PlayerInventories)

return function(player)
	local inventory = PlayerInventories[player.UserId]
	inventory = nil -- simulate inventory not loading (for testing purposes)
	
	if not inventory then
		player:Kick(
			'\n\n'..
			[[Your inventory failed to load. To prevent data loss, you were kicked.
			
			Please try joining again. If the issue persists, contact support.]]
		)
		task.cancel(coroutine.yield()) -- dont return
	end

	return inventory
end

GetPlayerInventory called by NewCharacter module:

return function(character)
	local player = game.Players:GetPlayerFromCharacter(character)
	if not player then return end
	
	-- give player inventory items
	for sku, playerItemInfo in pairs(GetPlayerInventory(player)) do -- function called here
		...
	end
end

No coroutine is being created anywhere.

I think I know why that works, the argument itself is doing the yield:

local function test(arg)
    print("Hello") -- This never prints
    return true
end

print(test(coroutine.yield())) -- So naturally this won't print either

I don’t know if the yield happens at test(coroutine.yield()) or when arg is being retrieved, I think it’s the former. So with this you are still causing memory leaks, which is why you need to use coroutine.create().

Ahh, I see.

Is there any reason why you advise using coroutine.resume(coroutine.create()) instead of coroutine.wrap()()?