confused by using statement

Hello,

Finally, I've got myself to clean up the 'using' statements sections of my *.mc files because I felt I was using some of these using clauses excessively.

I ended up with the following unanswered questions.

Q1: Why do I have to (as an example) write 'using Toybox.System;', at the beginning of my *.mc file, in order to use 'System.println()' function, but I do not have to write 'using Toybox.Math;' in order to use Math.round() function?

Q2: Could it be that Math module is implicitly made available by including some other module, which is itself including Math module inside?

My data field application is running in the simulator right now, as I am writing this post, which is using a ton of Math functions, without single usage of 'using Toybox.Math;'.

Other modules I am using (and I really must have included them explicitly to avoid compile time or run time error of missing symbol) are:

  • Toybox.System
  • Toybox.Time
  • Toybox.Graphics
  • Toybox.Application
  • Toybox.WatchUI

Q3: Another question: Do I have to include module in every *.mc file, where I intend to us it's assets, or is it enough to include the module once per application? If latter applies, does it matter in which file (entry point file, any file)?

I ended up randomly adding and removing 'using' statements from all my *.mc files to find minimal working configuration and I hate this lack of understanding and apparent lack of consistency.

Q4: Why are some cases of missing 'using' statement revealed at compile time (as compile time warning), but some only at run time (as exceptions)?

Can somebody give deeper and more comprehensive explanation of using 'using' keyword than found here in section 'Using Statements'?

https://developer.garmin.com/connect-iq/programmers-guide/monkey-c/

Thanks.

  • Q1: Why do I have to (as an example) write 'using Toybox.System;', at the beginning of my *.mc file, in order to use 'System.println()' function, but I do not have to write 'using Toybox.Math;' in order to use Math.round() function?

    This relates to the way that the compiler and the virtual machine locate symbols.

    The compiler will generate an error if you try to extend a class from a module that isn't exposed via a using statement. i.e., this will not compile:

    // using Toybox.Applicaton;
    
    class TestApp extends Application.AppBase {
    
        function initialize() {
            AppBase.initialize();
        }
    
        function getInitialView() {
            return [ new WatchUi.View() ];
        }
    }
    
    // BUILD: ERROR: vivoactive3: C:\Workspace\Test\source\TestApp.mc:3: Unable to find parent class of Application.AppBase for class TestApp

    If you uncomment the using statement and you have warnings enabled, you'll see a warning...

    BUILD: WARNING: C:\Users\vitek\Desktop\eclipse_3.1.6\eclipse-workspace\Test\source\TestApp.mc:10:  Unable to detect scope 'WatchUi.View' for the symbol 'initialize'.

    If you add a using clause for Toybox.WatchUi, the warning will go away. Note that this warning only occurs for classes as it is a warning about the 'initialize' function not being found. If you reference a function from a module (instead of a class), there is no warning/error.

    Both of these cases are handled by the compiler. The correct thing to do is to add the appropriate using clause for any module that you're using in your code, or to be explicit and fully qualify references to the class name (Toybox.Application.AppBase and Toybox.WatchUi.View).

    References to functions from a module (like System.println() or Math.round()) are resolved by the virtual machine at runtime. The virtual machine searches for symbols (functions and member variables) at the current scope and expands the search outwards. It is something like:

    1. Search the local function scope (if in a function)
    2. Search the current class scope, and recursively search the base class hierarchy (if in a class)
    3. Search the current module scope, and all parent module scopes out to the global scope.

    Consider the following example where I reference the symbol System without a using clause and without explicit qualification:

    using Toybox.Application;
    using Toybox.WatchUi;
    
    //using Toybox.System
    
    function f() {
        System.println("f");
    }
    
    class F /* extends Toybox.Lang.Object */
    {
        function initialize() {
        }
    
        function f() {
            System.println("F::f");
        }
    }
    
    class TestApp extends Application.AppBase {
    
        function initialize() {
            AppBase.initialize();
        }
    
        function getInitialView() {
            var f = new F();
            f.f();
    
            f();
    
            return [ new WatchUi.View() ];
        }
    }

    If I run this, I get the following runtime output:

    F::f
    
    Error: Symbol Not Found Error
    Details: Could not find symbol 'System'
    Stack: 
      - f() at C:\Workspace\Test\source\TestApp.mc:7 0x10000014 
      - getInitialView() at C:\Workspace\Test\source\TestApp.mc:30 0x1000008f 
    

    Notice that there was no problem referencing `System` from within class `F`, but there was a failure when using it from the global function `f`? If you consider the searching rules I outlined above, this makes sense.

    In the case of the function `f`, the search for `System` is something like this:

    1. Search the local function scope; Is there a symbol named `System` in `f`? No.
    2. Search the current class scope and base class hierarchy; Does not apply since we are not in a class.
    3. Search the current module scope and all parent module scopes; Is there a symbol named System in the global scope `$`? No. There are no parent scopes to search, so the search for symbol `System` is complete and has failed.

    If you consider the case of the function `f` in class `F`, the search is as follows:

    1. Search the local function scope; Is there a symbol named `System` in `F::f`? No.
    2. Search the current class scope and base class hierarchy.
      1. Is there a symbol named `System` in `F`? No.
      2. Is there a symbol named `System` in the implicit base class `Toybox.Lang.Object`? No.
      3. Is there a symbol named `System` in the enclosing module `Toybox.Lang`? No.
      4. Is there a symbol named `System` in the parent module `Toybox`? Yes. Search terminates after finding `Toybox.System`
    Q2: Could it be that Math module is implicitly made available by including some other module, which is itself including Math module inside?

    No. The issue is that all of the Toybox modules are implicitly found when you reference them from within a class function as demonstrated above.

    Q3: Another question: Do I have to include module in every *.mc file, where I intend to us it's assets, or is it enough to include the module once per application? If latter applies, does it matter in which file (entry point file, any file)?

    You should provide using clauses for every module you reference in each file that references that module. If you have a file A.mc that uses the `Toybox.System` module, and a file B.mc that uses `Toybox.Math`, you *should* put a `using Toybox.System` in A.mc and `using Toybox.Math` in B.mc. In some cases the compiler will enforce this (as demonstrated above with base classes and with class initializers). In other cases, this is enforced at runtime.

    The using statements don't necessarily have to be at the top or at global scope (you can use them in a module or class scope), but they are at the top of the file by convention.

    Q4: Why are some cases of missing 'using' statement revealed at compile time (as compile time warning), but some only at run time (as exceptions)?

    Sometimes the compiler needs to know about the object type. If it doesn't know what type you are referencing, it may not be able to proceed. In these cases it will issue an error. In some cases it will issue a warning because it knows the virtual machine will have a better chance of finding the thing that you are referencing.

    The bottom line is that you don't have to always use using statements for modules that you are referencing, but you should do it anyway. This is really not that different from #includes in C. You don't always need to include stdlib.h because other headers may implicitly include it. But, if you are referencing symbols that are supposed to be declared in stdlib.h, you should always explicitly include it.

  • Hello Travis.ConnectIQ,

    I've finally found a moment to thoroughly read through your reply and it was really helpful.

    Especially the part on why symbols are recognised inside a class (as opposed to outside class) even though their defining module is not referenced.

    Thanks!