You are correct in that you can't return null, which is because you have declared in your method that it returns a float. So, yes, removing the type declaration so it returns a variant is an option.
If you do this, remember that you should check null.
Be aware that operating with null will cause an error. But since this error happens when you try to use null it is away from the cause of the null. Which does not make for the most useful of errors.
Also the Godot editor will show green line numbers on the lines that are type-safe, and show gray line numbers otherwise (i.e. using variant or unsafe casts), which is useful when reviewing your code.
You could return NAN (not a number) which would allow you to keep the float type. With a couple caveats:
- Consider if
NAN might be a valid result in some cases. - Remember that you need to check for
NAN. And nothing is equal to NAN, in fact one of the ways to check if a variable is NAN is checking if it is different to itself, or you can use the more readable is_nan(…) method.
Similarly to null, the errors that come from NAN happen away from the cause. However operating with NAN will result in more NAN. Which is arguably worse that using null. Thus, if you can help it, please don't use NAN.
You are also correct that Godot does not have exceptions. You might, however, use push_error and return. Which leads to a question: why would you use push_error?
If you come from language that has exception, you might be familiar with the idea that some exceptions are intended to help the developer. For example "argument out of range" exceptions. You should not catch those exceptions. Instead, you should fix the code so they don't come up in runtime. However, you still want it to show during testing and debugging. And, of course, if some of them sneak into production, you want them to show up in logs.
You get that with push_warning and push_error. They will show up in the debugger, and also in logs. And you cannot catch them like exceptions, because they aren't exceptions.
You might also consider using assert for this purpose, or you can use the breakpoint keyword, given that those will interrupt in the debugger.
Option types and similar approaches has been proposed, but - at the time of writing - have not been implemented.
You could have a custom return type. For example you can define a nested class (add them at the bottom of the same file) with a float and a bool.
In Godot 3.x:
class OptionalFloat: var has_value:bool var value:float setget set_value func set_value(mod_value:float) -> void: value = mod_value has_value = true
In Godot 4:
class OptionalFloat: var has_value:bool var value:float: set (mod_value): value = mod_value has_value = true
And then use it like this:
func get_my_float(c) -> OptionalFloat: var result := OptionalFloat.new() var idx := p.find(c) if idx == -1: return result for i in range(0, idx): # … result.value = something # … return result
The examples above do not handle the case where you try to consume the value without checking if there is any. You can define a getter alongside the setter to do that. And perhaps use push_error there.
Be aware that these optional types would also require some kind of check (similar to checking for null or checking for NAN).
OK, you could do that. In particular if you really want to keep the type information. However, it is not a common approach. Instead people often fallback to variant, since it can represent a float (or whatever type) or null, also has some overhead, but you have to write less.
So Godot does not have out parameters. In fact, you cannot choose to pass by reference. But you can pass an array!
func get_my_float(c, result:Array = []) -> void: var idx := p.find(c) for i in range(0, idx): # … result.append(something) # …
Notice here that result is optional (it has a default value).
In Godot 4 you could use a typed array (Array[float]).
That is the approach that we would use if we need the return value, and also we need to have some additional optional output. In fact, this approach is used in parts of Godot API.
Ah, but in this case we have left the result as void here. So we could use the return value instead. That is, we might return an array (which could be empty for no result):
func get_my_float(c) -> Array: var result := [] var idx := p.find(c) if idx == -1: return result for i in range(0, idx): # … result.append(something) # … return result
Here, since we are returning, we could use packed arrays. In Godot 3 you can use a PoolRealArray. And in Godot 4, the equivalent of the PoolRealArray is either PackedFloat64Array or PackedFloat32Array (in Godot 3 which one you get depended on whether or not you had a 64 bits build or 32 bits build). And I point out we can use them when we are returning because these are value types.
Be aware that this does not eludes a check either, since you would have to check if the array is empty.
You might be putting the effort in the wrong place. If you make your function take the index in a parameter, how to handle when the value is not found becomes a problem of the layer above… There you have better context and could pick an appropriate action. So the method at hand only needs to handle the "argument out of range" situation.
Speaking of appropriate actions. I don't know what you are doing, because I lack said better context… But given the method name, an option that comes to mind is set a fallback default result:
func get_my_float(c, fallback := 0.0) -> float: var idx := p.find(c) if idx == -1: return fallback for i in range(0, idx): # … return my_float
Some examples of possible default values which might make sense depending on the context: 0.0, 1.0, INF, -INF, NAN.
Alternatively, consider if you should think of a get_or_add_my_float method. Or something like that.
Another approach you might consider is to pass continuation callbacks.
Godot 4 has the Callable type, inline anonymous methods ("lambda"), and you can pass methods as Callable directly.
In Godot 3 you can use the FuncRef type and the funcref method to create them.
You know better than me what the situation is. I have given you options, you decide.