LEVIATHAN v962456e · 962456eee1

Declarations

Activating a namespace's bind with use

Importing a type with use also installs the binding its namespace exports for that type.

since 0.1.0-alpha.1linuxwindowswasm

Description

A factory binding written at the top level of a namespace body is that namespace's exported binding for the type. When code imports the type with use NS::T; (or use NS::T as A;) and T is a class or interface, the namespace's binding for T is installed in the scope that contains the use, exactly as if bind T => <that factory>; (or bind A => ...; under an alias) were written there. Importing the type is therefore enough to receive its default implementation:

use activates the exported bind

namespace Svc {
    interface IClock { int now(); }
    class RealClock : IClock { int now() => 1000; }
    bind IClock => RealClock();
}
use Svc::IClock;
class FakeClock : IClock {
    int t;
    new FakeClock(int t0) { t = t0; }
    int now() => t;
}
void report(IClock clock) { console.writeln(clock.now()); }
void run() {
    bind IClock => FakeClock(7);
    report();
}
report();
run();
report();
1000
7
1000

report() is filled from the activated binding. Inside run, a bind of the same type is nearer and shadows it.

Rules

  1. Only a selective use of a type activates. uses NS; (including the implicit uses std;) never activates a binding. use NS::fn; of a name that is not a type imports the name and activates nothing. A use of a type whose namespace has no top-level binding for it imports the name and activates nothing, silently.
  2. A written binding beats an activated one in the same scope, without error. The duplicate-binding error applies only to two bindings written in the source.
  3. Two activated bindings cannot collide: bindings are keyed by type, and importing the same type twice gives the same single binding.
  4. Nearest wins is unchanged: an activated binding shadows and is shadowed exactly like a written one at the same position.
  5. An alias changes only the name: use NS::T as A; activates the same binding.

The standard library uses this: use std::IConsole; activates the system console binding, and IEnv, IClock, IFileSystem and INet work the same way.

Examples

uses imports names but never activates a binding:

uses does not activate

namespace Svc {
    interface IClock { int now(); }
    class RealClock : IClock { int now() => 1000; }
    bind IClock => RealClock();
}
uses Svc;
void report(IClock clock) { console.writeln(clock.now()); }
report();    // error: missing required argument 'clock'
not run — a compile error on purpose

An alias, a function import that activates nothing, and a written binding that beats the activated one:

Alias, non-type use, and written bind wins

namespace Svc {
    interface IClock { int now(); }
    class RealClock : IClock { int now() => 1000; }
    class Other : IClock { int now() => 5; }
    bind IClock => RealClock();
    int helper() => 3;
}
use Svc::helper;
use Svc::IClock as Clock;
void report(Clock clock) { console.writeln(clock.now()); }
report();
console.writeln(helper());
void swapped() {
    bind Clock => Svc::Other();
    report();
}
swapped();
1000
3
5

Substituting a recorder for the system console in a test:

Testing with a fake system capability

use std::IConsole;
class Recorder : IConsole {
    Array<string> lines;
    void write(string s) { lines = lines.add(s); }
    void writeln(string s) { lines = lines.add(s); }
    void writeln() { lines = lines.add(""); }
}
void greet(IConsole out) {
    out.writeln("hello from greet");
}
void testGreet() {
    Recorder rec = Recorder();
    bind IConsole => rec;
    greet();
    console.writeln("recorded: " + rec.lines.length().toString());
    console.writeln(rec.lines.at(0));
}
greet();
testGreet();
greet();
hello from greet
recorded: 1
hello from greet
hello from greet

Notes

The capability interfaces are a disciplined alternative to the ambient console, env, File and socket globals, not a sandbox: the globals remain reachable whether or not a binding is in scope.

Not implemented: binding objects that aggregate several bindings (Bindings values and bind someBindings;). Write ordinary factory bindings, or use use-activated ones, instead.

See also

  • Dependency injection: bind and inject — Declare lexically scoped factory bindings that fill unbound parameters automatically, and select a binding explicitly with inject.
  • Imports — uses and use — Bring a namespace's names into scope in bulk with uses, or one name at a time with use ... as ....
  • IConsole — What a program may say to the terminal.
  • IEnv — What a program may learn from the process environment: its arguments and its variables.
  • IClock — What a program may know about the time.
  • IFileSystem — What a program may do with the file system: open files and check whether a path exists.
  • INet — What a program may do with the network: open connections and listen for them.