The Windows Runtime allows parameters to be declared as out
, which means that the variable is passed by reference and will be written to, but not read from, by the method. (At the ABI layer, the variable is passed by address.)
runtimeclass MyClass { Boolean TryGetCount(out Int32 count); }
Many languages support passing variables by reference, and the projection aligns with those language features.
// C# int count; if (c.TryGetCount(out count)) ... // C# 7.0 if (c.TryGetCount(out int count)) ... // Visual Basic Dim count as Integer If c.TryGetCount(count) Then ... End If // C++/WinRT int count; if (c.TryGetCount(count)) ... // Rust/WinRT let mut count = 0; if (c.TryGetCount(&mut count)) ...
JavaScript, on the other hand, does not support passing variables by reference. To work around this, any method that has an out
parameter is rewritten by returning a JavaScript object with a property called result
which contains the original return value, and with additional properties, one for each out
parameter. The original out
parameters disappear from the formal parameter list.
var retVal = c.tryGetCount(); if (retVal.result) { console.log(retVal.count); }
The return value of the original TryGetCount
method is recorded as the result
property, and the count
that was returned by the original method becomes a count
property.
The name of the formal parameter is not just a documentation nicety in JavaScript. The name of the formal parameter is part of the programming interface because it becomes the name of the property!¹
Python also doesn’t support passing variables by reference. It performs a transform similar to JavaScript, but instead of returning an Object, it returns a tuple.
// Python/WinRT result, count = c.try_get_count();
This awkwardness with output parameters in JavaScript and Python makes output parameters slightly less attractive in the Windows Runtime.
¹ In the Windows Runtime, parameter names are considered part of the interface, and changing parameter names is a breaking change. We saw how JavaScript can be affected by changing the name of a formal parameter. The name is also programmatically significant in C#, since C# lets you pass parameters by name, which is particularly handy for parameters of numeric or Boolean type.
var trigger = new TimeTrigger(freshnessTime: 60, oneShot: true); var inkPoint = new InkPoint(position, pressure: 0.5, tiltX: 0.0, tiltY: 0.0, timestamp: 0);
Another approach that could have been used is that you pass a JavaScript object to the method, and a specific property gets filled in with the output.
In fact, I think all 4 options (output reference, return object, return tuple, return hybrid object/tuple) could work for any of the languages:
out
,readonly record struct
, tuple with implicit field names, tuple with explicit field names<Out()> ByRef
,Structure
, tuple, named tuple&
,struct
,std::tuple
, specializestd::tuple_size
andstd::tuple_element
&mut
,struct
,(A, B, …)
,impl From<ReturnStruct> for (A, B, …)
{...}
, array, array or other iterable object with fields/properties setI think dotnet marshaller can do a similar thing: if there is a WinAPI method that is
HRESULT DoSomething(T1 a, T2 b, out T3 x)
you can declare it as
extern T3 DoSomething(T1 a, T2 b)
and it will work.
That would make it a double signature translation. I believe the signature
A B(out C d);
is managed and already means ABI ofHRESULT B([out] C *d, [out, retval] A *result);
.I’m curious why C# does not use nullables for Try* functions. My guesses are:
1. Nullables didn’t exist in the .NET version where the Try* standard was established (I didn’t cheat and look up when nullables were introduced, but they are generic which wasn’t introduced until 2.0 I believe).
2. Using a nullable or tuple would result in memory allocation or otherwise poorer performance rather than just using a boolean and an out parameter.
3. What if you had a Try* actually fetching a nullable (for example, Dictionary can have any type of values you want), then you’d have a nullable nullable, that would be awkward.
Because there’s no guarantee that the semantics of the output of a Windows Runtime method
bool TryAbc(out int xyz)
are either(false, irrelevant)
or(true, relevant)
— it could well be(false, relevant)
and(true, irrelevant)
.The way the try functions work allow for elegant code like this:
Or this one:
But with recent advances to the C# language, a nullable-returning version would let you do:
Or:
This is not correct. Your code is testing whether or not the TryParse succeeded when the goal is to test the result of the conversion.
My code is intended to illustrate how calling the *hypothetical* null-returning versions would compare to calling the *real* versions shown in Markus’s post.
It’s slightly clouded by the fact that it’s `bool.TryParse` that’s being called; it’s easy to confuse the *”returned true because the parse succeeded”* result of the real method with the *”returned true because the parse succeeded and the parsed value was true”* of the hypothetical method.
Perhaps using the hypothetical version of `int.TryParse` might make it clearer:
Or:
What happens if there’s an `out` parameter named `result`?
My guess? That’s a problem for the programmer who has to port the API from C# to JS/Python.