UserProfile.getHeartRateZones crash when accessing return array

As you can see I get a crash when accessing the heart rate zones - this happens with a test app on my own device and it happens very rarely - my WF ran for a day while displaying the heartrate as complication...

Any ideas why this can happen?

1) Is it possible that UserProfile.getHeartRateZones may return null at some point?

2) can I rely on the docs regarding function return types like e.g. the on here: https://developer.garmin.com/connect-iq/api-docs/Toybox/UserProfile.html#getHeartRateZones-instance_function

It says the function can't return null - is that a guarantee?

  • I will try logging the exception ...

    Ok, good luck! 

    Is there a reason you can’t log the result of getHeartRateZones() though? If you’re worried about logging too much data, you could log the zones only when an exception occurs. I do think it’s also a good idea to log the exception.

  • Found the culprit - it was my code, it provided a class instance that is not a number to following function whenever I changed bands of my watchface because then the HR value become null and this was converted to a `DataError` instance... And this object was passed through to following function as `value`:

    private function calcHeartRateZone(value as Number, settings as WFSettings) as [String?, Graphics.ColorValue?] {
        // ...
    }

    Even though you see `:inline` in my screenshot, I did not use any features from prettier, so I can exclude any influence from it

    Now my conclusion (sadly) is following:

    Even if you enable strict mode, you can't rely on data types in all cases.

  • Can you provide more context for that statement about strict mode?

  • My monkey jungle looks like following:

    project.typecheck = 3
    project.optimization = 3pz

    Actually I must correct my last statement, following is not save (I just checked my local git history to make sure I check the exact code from back then):

    var value = valueObject as Number;

    This will not fail if `valueObject` is not a number... I do pass this value to the above function and only when it is accessed inside this function the exception is thrown...

  • This is why it's important to post your code. There's nothing wrong with the compiler. It lets you be the boss, and when you cast valueObject to be a Number it believes you. The bug is in the code. Actually my guess is that it did complain, and that's why you added the "as Number", haven't you?

  • I use a base class and because monkeyc does not support generics I use object as parameter and cast it... I still would expect something like a ClassCastException though... There are no cast types in monkeyc as far as I know - seems like the cast with the as operater behaves like a c style static_cast and not like a dynamic_cast...

  • then try something like:
    var value = valueObject.toNumber();

    you'll have to implement toNumber for your objects, but for Number, Double, Float, String, and many other built-in types it'll work.

  • The problem is, the class is too generic. It can handle data that comes from a complication as well as data that comes from a calculation formula... The type is too flexible. Generics would solve the issue, but otherwise I need a cast.

  • Sounds like adding a toString() that gives you back what you need to display might help 

  • Even if you enable strict mode, you can't rely on data types in all cases.
    I use a base class and because monkeyc does not support generics I use object as parameter and cast it... I still would expect something like a ClassCastException though... There are no cast types in monkeyc as far as I know - seems like the cast with the as operater behaves like a c style static_cast and not like a dynamic_cast...

    Correct, type casts in Monkey C are a purely compile-time operation. There is no concept of runtime casting (unlike Java, C# or C++). The runtime type of a variable is unaffected by any compile-time cast. (Similarly, compile-time types and runtime types are determined differently - compile-time types are determined by the compiler semantically analyzing the code, while runtime types are determined by a type field that belongs to every object.)

    In Monkey C, you can think of the type checker / Monkey Types as an additional layer of safety that only exists at compile time, similar to how types work in TypeScript, or how the type checker works in Python. This has its advantages (certain amount of backwards compatibility with old Monkey C code), but it also means that a type cast is simply a directive to the compiler to ignore whatever it thinks it knows about the type of the variable in question, and to listen to what the type cast says instead. ("The variable's type is definitely X. Source: trust me bro.")

    I would go so far to say that in Monkey C, type casts are harmful and should be avoided whenever possible, as they tend to make your code more dangerous (they can introduce runtime type errors as in this case, and they give you a false sense of security). (I would say the same about TypeScript as well.) Unfortunately, there are still some situations where type casts are required in Monkey C. It's also a shame that Garmin decided to use the same syntax ("X as Y") for type casts and type declarations (unlike typescript, for example). In contrast to type casts, type declarations make your code safer, in general.

    I will say that it would be an improvement if Monkey C would forbid casts between incompatible types (or forbid them), similar to what TypeScript does. (In TypeScript, you need an intermediate cast to "Any" if you want to cast between incompatible types.)