How does JavaScript represent output parameters in the Windows Runtime?

Raymond Chen

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);

12 comments

Discussion is closed. Login to edit/delete existing comments.

  • Igor Tandetnik 1

    What happens if there’s an `out` parameter named `result`?

    • Dan Bugglin 0

      My guess? That’s a problem for the programmer who has to port the API from C# to JS/Python.

  • Dan Bugglin 0

    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.

    • Markus Schaber 0

      The way the try functions work allow for elegant code like this:

      if (bool.TryParse(args[1], out var value) && value)
          DoSomething();

      Or this one:

      if (!bool.TryParse(args[1], out var value))
          throw new SomeException()
      
      DoSomethingWith(value);
      • Richard Deeming 0

        But with recent advances to the C# language, a nullable-returning version would let you do:

        if (bool.TryParse(args[1]) is true)
            DoSomething();

        Or:

        if (bool.TryParse(args[1]) is not {} value)
            throw new SomeException();
        
        DoSomethingWith(value);
        • Oisin Grehan 0

          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.

          • Richard Deeming 0

            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:

            // Real version:
            if (int.TryParse(args[2], out int i))
                DoSomethingWith(i);
            
            // Hypothetical version:
            if (int.TryParse(args[2]) is {} i)
                DoSomethingWith(i);

            Or:

            // Real version:
            if (!int.TryParse(args[2], out int i))
                throw new SomeException();
            
            DoSomethingWith(i);
            
            // Hypothetical version:
            if (int.TryParse(args[2]) is not {} i)
                throw new SomeException();
            
            DoSomethingWith(i);
    • GL 1

      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).

  • Mike Makarov 0

    I 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.

    • GL 0

      That would make it a double signature translation. I believe the signature A B(out C d); is managed and already means ABI of HRESULT B([out] C *d, [out, retval] A *result);.

  • Neil Rashbrook 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.

    • Solomon Ucko 0

      In fact, I think all 4 options (output reference, return object, return tuple, return hybrid object/tuple) could work for any of the languages:

      • C#: out, readonly record struct, tuple with implicit field names, tuple with explicit field names
      • Visual Basic: <Out()> ByRef, Structure, tuple, named tuple
      • C++: &, struct, std::tuple, specialize std::tuple_size and std::tuple_element
      • Rust: &mut, struct, (A, B, …), impl From<ReturnStruct> for (A, B, …)
      • JavaScript: caller passes object and callee sets field value, {...}, array, array or other iterable object with fields/properties set
      • Python: caller passes object and callee sets field value, dataclass, tuple, namedtuple

Feedback usabilla icon