-1
The added value doesn't feel like it outweighs the downside of another "magic" hidden variable with a name someone thought was obvious but everyone else has to try and remember.
The comment about it being "fairly niche" feels accurate. This means that code using it will be less readable and understandable to the majority who are outside this small niche when they have to read code produced by the few in the niche who use this.
Explicit code is easier for others to understand and maintain after the original author leaves the project.
Here's a possible alternative to the first alternative presented by the OP that feels less awkward and doesn't have the same issues that one does:
@discardableResult
private func result<T>(_ result: T) -> T {
print("result is: \(result)") // or whatever your trace does
return result
}
then can change the sample use case to something that seems clear, doesn't require defining a result variable, and doesn't allow for forgetting to assign that variable:
func foo2(param1: Int, param2: Int?, param3: UnsafePointer<Float>?) -> Int {
guard param1 > 0 else {
return result(-1)
}
guard let param2 = param2 else {
return result(-2)
}
guard let param3 = param3 else {
return result(-3)
}
return result(some(param1, param2, param3))
}
Though a more explicit name like:
private func tracedResult<T>(_ result: T) -> T {…}
(or something similar) would be more appropriate given my claim that explicit code is easier for others to maintain :)
One could also define multiple different tracers this way. Such as:
@discardableResult
func tracedResult<T>(_ result: T,) -> T {
print("TRACE> result is: \(result)") // or whatever
return result
}
and:
@discardableResult
func tracedResult<T>(_ result: T,
_ message: @autoclosure () -> String = "",
_ file: StaticString = #file,
_ line: UInt = #line,
_ function: StaticString = #function)
-> T {
print("TRACE> \(message())")
print("TRACE> returning from \(function) in \(file)\nTRACE> at line: \(line) with result: \(result)")
return result
}
and the example function could become:
func foo3(param1: Int, param2: Int?, param3: UnsafePointer<Float>?) -> Int {
guard param1 > 0 else {
return tracedResult(-1, "Error: param1 needs to be greater than zero")
}
guard let param2 = param2 else {
return tracedResult(-2, "Error: param2 needs to be non-nil")
}
guard let param3 = param3 else {
return tracedResult(-3, "Error: param3 needs to be non-nil")
}
return tracedResult(some(param1, param2, param3))
}
If I call it with bad param 3:
foo3(param1: 10, param2: 12, param3: nil)
get output of:
TRACE> Error: param3 needs to be non-nil
TRACE> returning from foo3(param1:param2:param3:) in MyPlayground.playground
TRACE> at line: 1961 with result: -3
This also lets you just return a value without tracing in a way that is easily readable as such for unusual cases where you maybe called tracedResult
manually or did some other manual logging.
Anyway, just an alternative idea to one work-around presented by OP that seems to address most of the issues raised without requiring new language functionality. Likely others have better ones.
(Note: if the example was a throwing function than you could define a similar function that was passed the error to throw and returned it so could throw via: throw tracedError(Errors.someError)
to have a traced throw).