Tour

There is always where you want to start with a new language - what will Denim look like? The answer is a lot like Rust. I just happen to think that Rust gets a lot of stuff right. That said, expect some deviations made in the interest of developer ergonomics and Denim's particular domain challenges.

Primitives

Denim's primitives are mostly stolen from Go. It has:

  • bool
    Boolean value that can be true or false.

    A bool literal is either true or false.

  • byte
    An 8-bit unsigned integer, often in strings. NOTE: in the context of a string, byte does not encapsulate the semantic concept of a "character" since in some encodings, like UTF-8, a character may be expressed with more than one byte.

    A byte literal is just a non-negative number like 128.

  • float
    A 64-bit signed floating point number.

    A float literal is either dot-delimited decimal numbers like -12.80001, or scientific notation like 1.2e-12.

  • int
    A 64-bit signed integer number.

    An int literal is just a number like 11 or -7. Notably, Denim does not support binary, hex, or octal int literals.

  • rune
    A 32-bit unsigned integer number intended to represent the semantic concept of a "character" in strings.

    A rune literals is just a single-quoted character like 'k' or 'πŸ’©'.

  • string
    A sequence of bytes semantically associated with text.

    A string literal is usually a quoted span of text like "hello world", but it comes in other flavors too.

    "\"With escaped characters\t";
    
    """A
    multiline
    string""";
    
    r#"
     a raw string where \n escaped characters are not a thing
     "#;
    

    Denim also supports Dart-style string concatentation. You can concatentate string literals by declaring them adjacent to one another. Though many other languages support it however, Denim will not support string concatentation via the + operator.

    "this a long string that I'm' worried will end up exceeding the line limit, "
    "but luckily I can just continue with another adjacent string on the "
    "next line";
    

    string interpolation will look very familar to fans of ES2015+ or Dart.

    "${1 + 2 + 3 + 4 + 5} is fifteen";
    
    "You can also reference $some_variable without the `{}`";
    

Special primitives

It is important to note that Denim does not have a null-type like Go's nil. The closest idea that Denim has in this regard is the void. The void type has exactly one value, void, and is used when there is no other meaningful value that could be returned. void, much like Rust's (), is most commonly seen implicitly: functions without a -> implicitly have a void return type.

#![allow(unused)]
fn main() {
// The functions below are equivalent:

fn long() -> void {}
fn short() {}
}

Denim also steals the unknown type from TypeScript. unknown represents all possible values. Every type is assignable to type unknown. Therefore the type unknown is a universal supertype of the type system. However, the Denim compiler won't allow any operation on values typed unknown - the values must first be cast to a narrower type. For more on this concept, check out some information on TypeScript's unknown.

Variables

Denim steals variable declaration from Rust.

#![allow(unused)]
fn main() {
let abc = 123;
}

Like in Rust, Denim's let creates immutable variables by default. This means that abc cannot by be assigned a new value.

#![allow(unused)]
fn main() {
let abc = 123;
abc = 321; // Compile-time error
}

To create a mutable variable, you need to add a mut prefix to the let keyword.

#![allow(unused)]
fn main() {
let mut xyz = 123;

xyz = 456; // πŸ‘
}

Importantly, Denim does not have a notion of const. Instead let is also used to declare constants at the top-level lexical scope,

Printing

Denim ships with one main way to print out to the console - print.

print is a function (explained in greater depth later) that takes a string which it prints to its own line in the console. In the browser, this means it calls console.log(...) under the hood. Outside of the browser, it appends a line to stdout.

#![allow(unused)]
fn main() {
print("hello world"); // Prints "hello world" on its own line.
}

It is worth noting that more sophisticated logging facilities are available via library.

Tuples

Tuples are a fixed-size collection of different types. They can be helpful in situations where you want to group a few different pieces of data without creating a dedicated, named data structure for them. Rust Tuples are great. Why mess with a good thing? Denim Tuples are functionally identical.

#![allow(unused)]
fn main() {
// Tuple of an `int`, a `string`, and a `bool`. By the way, `let` is how we
// create variables in Denim. We'll elaborate in depth a little later.
let tuple = (123, "456", true);

// You can read different parts of a Tuple with dot notation.
print(tuple.0); // Prints "123"
print(tuple.1); // Prints "456"
print(tuple.2); // Prints "true"

print(tuple.7); // Compile-time error
}

While Denim Tuples are always immutable, they can be quite ergonomic to use and manipulate. Tuples can be composed together via the ... operator, and split apart in a similar way through de-structuring.

#![allow(unused)]
fn main() {
let x = (1.2e-3, 'e', false);
let y = ("yo", ...x, x.1);

print(y); // Prints "(yo, 0.0012, e, false, e)"

let (first, ...middle_stuff, last) = y;

print(first); // Prints "yo"
print(third); // Prints "e"

// NOTE: `middle_stuff` is itself a Tuple.
print(middle_stuff); // Prints "(0.0012, e, false)"
}

Array

Perhaps the most common collection in most languages is an array - an ordered sequence of values that supports random access. In JavaScript, it is called Array while in Rust it is called Vec. Denim Arrays should look feel and behave like Dart's List or JavaScript's Array.

#![allow(unused)]
fn main() {
let array = [1, 2, 3];
}

Like in other languages, Denim Arrays support random access by index with the [] operator.

#![allow(unused)]
fn main() {
// The type of `array` is inferred to be `[int]` here.
let array = [1, 2, 3];

print(array[0]); // Prints "1"
print(array[2]); // Prints "3"

print(array[17]); // Compile-time error
}

Need your Array to be mutable? Prefix the literal with a mut.

#![allow(unused)]
fn main() {
let mutable_array = mut [1, 2, 3];
}

Sometimes when you have a mutable Array, it starts empty. In this situation, the inner type of the Array is ambiguous, so it falls back to unknown by default. You can help provide more type information on the variable or explicitly cast the Array literal to correct this.

#![allow(unused)]
fn main() {
let another_array: mut [string] = mut [];
// In Denim, like in Rust, you can cast a value with the `as` keyword.
let yet_another_array = [] as [bool];
}

Denim can also infer the inner type of the Array later on from context.

#![allow(unused)]
fn main() {
// `one_more_for_the_road` is starts as a `mut [unknown]`.
let one_more_for_the_road: = mut [];
// Now Denim knows that `one_more_for_the_road` must be an `mut [int]`.
one_more_for_the_road.add(2)
}

Denim Arrays have lots of helpful methods focused on mutation.

#![allow(unused)]
fn main() {
let some_array: mut [int] = mut [];
some_array.add(2);
some_array.add(1);

print(some_array); // Prints "[2, 1]"

some_array.remove_at(0);

print(some_array); // Prints "[1]"

print(array[2]); // Prints "3"
print(mutable_array[0]); // Prints "2"
}

Denim Arrays ship with special syntax to instantiate arrays with a fixed number of identical values.

#![allow(unused)]
fn main() {
let eight_zeroes = [0; 8]; // `eight_zeroes` is an `[int]`
let four_strings = [""; 4]; // `eight_zeroes` is an `[string]`
}

Denim Arrays are spreadable with ... just like JavaScript arrays.

#![allow(unused)]
fn main() {
let x = [1, 2, 3];
let y = [...x, 4, 5, ...x, 6];

print(y); // Prints "[1, 2, 3, 4, 5, 1, 2, 3, 6]"
}

Denim allows for ergonomic slicing and dicing of arrays via de-structuring. Denim Array de-structuring is very similar to JavaScript Array de-structuring.

#![allow(unused)]
fn main() {
let y = [1, 2, 3, 4, 5, 6];

let [first, second, ...middle_stuff, last] = y;

print(first); // Prints "1"
print(second); // Prints "2"
print(last); // Prints "6"

// NOTE: `middle_stuff` is itself an Array.
print(middle_stuff); // Prints "[3, 4, 5]"
}

Enums

Enums are a great way to model data that is best described in categories. For example, the concept of "days of the week" could be accurately described a string, but since there are only seven kinds of them, enum is a better fit. enum allows you to explicitly enumerate each variant.

#![allow(unused)]
fn main() {
enum DayOfTheWeek {
  Monday,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday,
  Sunday,
}
}

Broadly speaking, explicit enumeration via enum makes validation and pattern matching over your data more ergnomic and less error prone. Sometimes, it makes sense to also attach data to each variant, allowing enum to function more like structs or classes in other languages.

#![allow(unused)]
fn main() {
enum IpAddress {
  // V4 IP addresses look like "192.168.1.1".
  //
  // Each `IpAddress::V4` has an inner field of type `[int]`.
  V4([int]),
  // V6 IP addresses look like "2001:db8::2:1".
  //
  // Each `IpAddress::V6` has a `segments` field of type `string` and a
  // `segment_count` of type `int`.
  V6 { segments: string, segment_count: int },
}

let some_v4_address = IpAddress::V4([192, 168, 1, 1]);

print(some_v4_address.0); // Prints "[192, 168, 1, 1]"

// `segments` does not need to be explicitly specified since it is the only
// field of `IpAddress::V4`.
let some_v4_address = IpAddress::V6 {
  segments: "2001:db8::2:1",
  segment_count: 5,
};

print(some_v6_address.segments); // Prints "2001:db8::2:1"
print(some_v6_address.segment_count); // Prints "5"
}

Special enums

TODO(skeswa): flesh this out by copying (Option, Result) from rust.

#![allow(unused)]
fn main() {
pub enum Option<T> {
  None,
  Some(value: T),
}

pub enum Result<T, E> where E = Error {
  Err(error: E),
  Ok(value: T),
}
}

We have special syntactic sugar for Option:

#![allow(unused)]
fn main() {
// This:
let x: Option<string>;
// Is the same as this:
let x: string?;
}

Option is used in much of the same way that null, nil, or undefined is used in other languages - to communicate that a value may be absent. Unfortunately, dealing with Option and other similar ideas is sort of awkward. You end up writing a lot of if-this-then-that logic dealing with whether the value is there or not.

if (!thing.x || !thing.x.y || !thing.x.y.z) {
  return "nope";
}

return thing.x.y.z.value;

To reduce the cruft, lanuages like JavaScript added the ?. and ?? operators.

return thing.x?.y?.z?.value ?? "nope";

Better right? Well, sure. Except that now you have two operators that basically do the same thing depending on context - . and ?.. They enable access into the state and behavior of values and objects.

In Denim, all instances of . behave like ?. does in other languages - it falls back to Option::None automatically when an optional value is absent. Denim retains ?? as syntactic sugar for Option::unwrap_or(..):

#![allow(unused)]
fn main() {
thing.x.y.z.value ?? "nope";
}

Maps

TODO(skeswa): flesh this out (Dart Maps).

#![allow(unused)]
fn main() {
// Below, `x` is inferred to be of type `{string: int}`.
let x = {
  "one": 1,
  "two": 2,
  "three": 3,
};

let y: {string: unknown} = {
  1: "one",
  2: [1, 2],
  3: {"hello": "world"},
};

print(x["one"]); // Prints "Some(1)"
print(y[2]); // Prints "Some([1, 2])"

print(x["four"]); // Prints "None"
print(y[7]); // Prints "None"
}

Denim Maps, like other Denim data structures, are made mutable with a mut prefix.

#![allow(unused)]
fn main() {
let mutable_map = mut {"one": 1, "two": 2};
}

Mutable Denim Maps feature useful methods and operators stolen from the Maps of other languages.

#![allow(unused)]
fn main() {
let mutable_map: mut {string: string} = mut {};
mutable_map["hello"] = "world";
mutable_map["foo"] = "bar";

print(mutable_map); // Prints "{hello: world, foo: bar}"

mutable_map.remove_key("hello");

print(mutable_map); // Prints "{foo: bar}"
}

TODO(skeswa): spread notation

#![allow(unused)]
fn main() {
let x = {
  "one": 1,
  "two": 2,
  "three": 3,
};
let y = {...x, "four": 4};
}

TODO(skeswa): destructuring

#![allow(unused)]
fn main() {
let {"one": one, "two": two, ...everything_else} = y;
}

Sets

TODO(skeswa): flesh this out (Dart Sets).

#![allow(unused)]
fn main() {
// The type of `x` is inferred to be `{string}` here.
let x = {"one", "two", "three"};
let y = mut {1, 2.2, 3};
}

TODO(skeswa): spread notation

#![allow(unused)]
fn main() {
let x = {
  "one": 1,
  "two": 2,
  "three": 3,
};
let y = {...x, "four": 4};
}

TODO(skeswa): no destructuring

Type Aliases

Denim allows you to come up with another name for an existing type using something called a type alias. Like Rust type aliases, Denim type aliases are declared with the keyword type. For example, the following defines the type Point as a synonym for the type (int, int):

#![allow(unused)]
fn main() {
type Point = (int, int);

let p: Point = (41, 68);
}

Type aliases can be useful to abbreviate long, verbose types. Type aliases also come in handy when attaching more semantic meaning or description to a common type, like a tuple in the case above, would make your code easier to reason about.

Operators

  • and, or
    Denim steals these logical comparison operators from Python. Why? Well, truth be told, it is mostly to reduce the ambiguity of || (see: Rust closure syntax). But also, I think it sorta reads nicely since keywords are highlighted to be pretty eyecatching usually.
  • ==, !=
    The strict equality and inequality operators work just the way that you think they do: they check if primitives are equal, or if non-primitives point to the same address in memory.
  • ===, !==
    These two operators are congruence and incongruence operators in Denim. They are meant to check if two values are qualitatively equal or not. We use the Eq trait to implement these operators.
  • +, -, *, /, and %
    Arithmetic operators can only be applied to numbers of the same kind.
  • **, the exponentiation operator, is stolen from Python
  • ~/, the truncating division operator, is stolen from Dart

You might be wondering where the bitwise operators are - there are none! Looking for operator overloads? You won't find them here.

Good riddance.

Comments

Comments are almost purely stolen from Rust.

#![allow(unused)]
fn main() {
// This is a simple line comment.

let abc /* this is an inline comment */ = /* stick these anywhere */ 123;

/// This is a doc comment.
///
/// These comments are intended to document your code in greater detail. To
/// facilitate this greater detail, these kinds of comments have:
/// 1. **full** _Markdown_ `support`
/// 2. Dart-style `[]` code links
///    For example, [abc] references the variable created above explicitly.
let forty_two = 42;
}

Expressions and statements

Following Rust's lead, Denim is (mostly) an expression language. Rust's documentation does a good job describing what this means, and some of its implications:

Most forms of value-producing or effect-causing evaluation are directed by the uniform syntax category of expressions. Each kind of expression can typically nest within each other kind of expression, and rules for evaluation of expressions involve specifying both the value produced by the expression and the order in which its sub-expressions are themselves evaluated.

In contrast, statements in Rust serve mostly to contain and explicitly sequence expression evaluation.

The quoted description can be a bit difficult to fully understand, but it basically boils down to a simple mantra: in Denim, almost everything, including control flow like if...else, is an "expression" can be used like a value. Expressions can be terminated, and their values contained, by capping them with a ; character. Loosely, a terminated expression is a "statement".

Perhaps the best way to visualize this is to demonstrate an example involving if...else, Denim's simplest branching control flow expression.

#![allow(unused)]
fn main() {
// Pretend that `some_random_int` is defined elsewhere and is a randomly
// generated `int`.
let x = some_random_int;

// Below, `message`'s value results from an `if...else if...else` expression
// on `x`. When `x` is `4`, `message` is `"x is four"`. Also, if `x` is not `3`
// or `4`, `message` is the empty string.
let message = if x == 4 {
  "x is four"
} else if x == 3 {
  "x is three"
} else {
  ""
};

// As you can see, expressions can also be used in a sort of stand-alone
// fashion.
if !message.is_empty() {
  print(message);
} else {
  print("Looks like there is nothing to say!");
}
}

Keywords

  • Stolen from Rust
    • as
    • async
      • Suffixable
    • await
      • Suffixable
    • break
    • continue
    • else
    • enum
    • extern
    • false
    • fn
    • for
    • if
      • Suffixable
    • impl
    • in
    • let
    • loop
    • match
      • Suffixable
    • mod
    • pub
      • pub(pkg) for directory-level visibility
      • pub(repo) for repository-level visibility
    • return
    • self
    • Self
      • Special type
    • struct
    • trait
    • true
    • type
    • use
    • where
    • while
      • Suffixable
  • Stolen from Dart
    • is
      • Used for Dart-style type checking
    • show
    • try
      • Suffixable
    • void
  • Stolen from Python
    • from
  • Stolen from TypeScript
    • unknown
  • Originals
    • fork
      • Suffixable
    • tandem

Suffixing

In Denim,some keywords can be applied as suffixes with . notation. Namely, async, await, fork, if, match, try, and while.

#![allow(unused)]
fn main() {
let some_eventual_function_value = async some_function("abc");

let some_other_eventual_function_value = some_other_function("abc").async;

print(await some_eventual_function_value);

print(some_other_eventual_function_value.await);

fork some_struct {
  some_field: "123",
};

some_struct.fork {
  some_field: "123",
};

if some_bool_expression {
  print("its bigger")
} else {
  print("its _not_ bigger")
}

some_bool_expression.if {
  print("its bigger")
} else {
  print("its _not_ bigger")
}

some_number.match {
  1..=122 => print("nope"),
  123 => print("yep"),
  _ => print("nope"),
}

match some_number {
  1..=122 => print("nope"),
  123 => print("yep"),
  _ => print("nope"),
}

fn x() -> Result {
  something_that_can_fail(123).try;
  try {
    let mut a = 123;
    a = a * 2;

    something_that_can_fail(123)
  }
}

let mut i = 0;
while i < 3 {
  print("i is $i");
}

let mut is_done = true;
is_done.while {
  is_done = false;
}
}

Functions

Syntactically, Denim functions are very similar Rust functions.

#![allow(unused)]
fn main() {
// Functions can specify a return type using the `->` symbol.
pub fn multiply_by_two(num: float) -> float {
  // Functions implicitly return the last value in their body. Since, the next
  // line is not terminated by a `;`, it evaluates to `num * 2`.
  num * 2
}

// No need to specify if a function is `void`, just say nothing at all:
fn print_hello_world() {
  // By the way, printing works the same way it does in Dart. `print` is a
  // globally visible function that takes a `string` and outputs it to
  // console/stdout.
  print("hello world");
}
}

There is just one wrinkle with Denim functions - there are no positional arguments, only named arguments that can appear in any order. When a function is invoked, its arguments must be explicitly labeled at the call-site unless a variable is passed along sharing the name of an argument. There is one exception to this rule: if a function has just a single argument, no label is necessary.

#![allow(unused)]
fn main() {
fn multiply_by_two(num: float) -> float {
  num * 2
}

print(multiply_by_two(3)); // prints "6"

fn multiply(a: float, b: float) -> float {
  a * b
}

print(multiply(a: 2, b: 5)); // prints "10"

let b = 5;

print(multiply(a: 2, b)); // prints "10"
}

There are situations where you need multiple arguments, but naming them would be a little silly. The best way to do this in Denim is to have a tuple as your only argument.

#![allow(unused)]
fn main() {
fn add(nums: (int, int)) -> int {
  nums.0 + nums.1
}
}

Denim functions can also have optional arguments. One way to accomplish this is to specify a default value for a parameter.

#![allow(unused)]
fn main() {
// Below Denim infers that `action` is a `string` from its default value.
//
// We explicitly specify a type for `times` because `= 1` would it an `int` by
// default.
//
// NOTE: default values must be immutable.
fn i_cant_wait_to(action = "take a nap", times: float = 1) {
  print("Time to $action $times time(s)!");
}

print(
  i_cant_wait_to(
    action: "eat donuts",
  ), // prints "Time to eat donuts 1 time(s)!"
);
print(
  i_cant_wait_to(
    action: "eat donuts",
    times: 1.5,
  ), // prints "Time to eat donuts 1.5 times(s)!"
);
print(i_cant_wait_to()); // prints "Time to take a nap 1 times!"
}

Another way to declare an optional argument is to make its type T?. T? represents an optional value of type T. We'll describe T? in greater depth later, but it works identically to how Rust's Option<T> works.

#![allow(unused)]
fn main() {
fn measurement(scalar: float, unit: string?) {
  let lowercase_unit = unit.to_lower() ?? "";

  "$scalar $lowercase_unit"
}

print(measurement(scalar: 12.5, unit: Some("px"))); // prints "12.5px"
print(measurement(scalar: 12.5, unit: None)); // prints "12.5"
}

Denim also includes some syntactic sugar to make using this a little less verbose by allowing Some or None to be implied by the respective inclusion or exclusion of an argument.

#![allow(unused)]
fn main() {
print(measurement(scalar: 12.5)); // prints "12.5"
print(measurement(scalar: 12.5, unit: "px")); // prints "12.5px"
}

Denim also has a convenient alternate syntax for anonymous functions, called lamba functions, that it borrows from Rust closures. Unlike Rust closures however, Denim lambda functions are just plain ol' functions with no extra or special behaviors.

#![allow(unused)]
fn main() {
fn sum_three(a: int, b: int, c: int) -> int {
  a + b + c;
}

let sum_three_as_a_lambda = |a: int, b: int, c: int| a + b + c;
}

The argument types of a lambda can be inferred if enough information is provided at the call site.

#![allow(unused)]
fn main() {
let inferred: fn(a: int) -> int = |a| a + 1;
}

By the way, function types look like this:

#![allow(unused)]
fn main() {
fn(a: A, b: B, c: C) -> D;
}

Denim has a special syntax for functions that receive an inlined anonymous function as an argument. This common when passing callbacks and in embedded DSLs. Any function argument named body can have an inline block after its invocation that works like a lambda function.

#![allow(unused)]
fn main() {
fn element(name: string, body: Option<fn() -> Element>) -> Element {
  Element { name, child: body() }
}

// This:
element(name: "div", body: || {
  element(name: "button", body: || {
    span("click me")
  })
})

// Is the same as this:
element("div") {
  element("button") {
    span("click me")
  }
}
}

Thanks to "getterification", we can make this syntax even sweeter:

#![allow(unused)]
fn main() {
fn button(body: Option<fn() -> Element>) -> Element {
  element("button", body)
}

fn div(body: Option<fn() -> Element>) -> Element {
  element("div", body)
}

div {
  button {
    span("click me")
  }
}
}

TODO(skeswa): it for the singular argument passed to body.

Modules

Denim's core packaging primitive is called a "module". Modules designed to define behavior, state, and types for a particular context or domain. Furthermore, Denim modules are intended to depend on each other through the import and export of behavior, state, and types.

Denim modules are expressed a file system as a directory with Denim source files. Each source file in an Denim module has full visibility of everything declared in the other source files of the module. Additionally, each source file can do its own importing and exporting. Denim source files can only import stuff exported by another Denim module. It might help to think of this style of packaging is a blend of Go packages and ES modules.

Like Deno and Go, remote modules are fetched from source control with an accompanying version. Version is typically inferred from a tag or reference on the imported module repository.

#![allow(unused)]
fn main() {
from "github.com/abc/xyz@v0.9.0" use { hello, world };
}

Denim allows imports from other modules in the same repository, too. These are called respository-relative imports. ~ always refers to the root of the repository containing the current module. Respository-relative imports do not specify a version because the version will always be the same as the current module.

#![allow(unused)]
fn main() {
from "~/bing/bang" use { boom: bΓΌm }; // Aliases `boom` to `bΓΌm`.
from "~/some/sub/module" use { something };

// You can use ... syntax to import "everything else".
from "github.com/foo/bar@v4.2" use { Foo, bar, ...foo_bar };
from "github.com/bing/bong@latest" use { ...bing_bong };
}

You can also re-export stuff using the show keyword.

#![allow(unused)]
fn main() {
from "github.com/abc/xyz@v0.9.0" show { hello };

from "~/bing/bang" show { boom: bΓΌm };
}

Like in Rust, you can export stuff from Denim modules with the pub keyword. Anything not declared with a pub will only be visible to things in its own module.

#![allow(unused)]
fn main() {
pub let stuff_outside_of_this_module_can_see_me = true;
}

Structs

You may now be wondering how more complex data structures are created and managed in Denim. I'm sure you are so shocked to find out that we (mostly) stole Rust syntax here too.

#![allow(unused)]
fn main() {
// We can make some or even all of the `struct` or its fields visible to
// external modules using the `pub` keyword.
pub struct User {
  active = false,
  /// You can put doc comments directly on `struct` fields.
  coolness_rating: int,
  pub name: string,
  pub nickname: string?,
}
}

We can instantiate and use struct like this:

#![allow(unused)]
fn main() {
let some_user = User {
  active: true,
  coolness_rating: 42,
  name: "Some User",
  nickname: None,
};
}

In the User struct above, two fields are optional - active and nickname. active is made optional by the specification of a default value for it, false. nickname is optional because it is of type Option<string>. Optional fields may be excluded when a struct is instantiated.

#![allow(unused)]
fn main() {
let sam = User {
  coolness_rating: 11,
  name: "Sam",
  // Since `nickname` is an `Option<string>` we can simply specify a `string`.
  nickname: "S-money",
};

let tria = User {
  active: true,
  coolness_rating: 12,
  name: "Tria",
};

print(tria.nickname); // Prints "None".

let jen = User {
  active: true,
  coolness_rating: 13,
  name: "Jen",
  // Below, `"Jenners"` is automatically wrapped in a `Some(..)`.
  nickname: "Jenners",
};
}

Denim sugarifies Rust's default impl by simply allowing you to declare methods within the struct itself. This should feel familiar to anyone coming from a language that makes heavy use of classes.

#![allow(unused)]
fn main() {
struct Car {
  make: string,
  model: string,
  owner: User,

  // `self` is a special argument. Like in Rust, it means that this method is
  // attached to any instance of `Car`.
  fn drive(self) {
    print(
      "A ${self.model} ${self.model} owned "
      "by ${self.owner.name} is driving",
    );
  }

  // Functions defined within a `struct` don't have to be attached to a
  // particular instance of that struct. They can instead function like static
  // methods in other languages. For instance, you would invoke this function
  // with `Car::create_lemon_for(some_user)`.
  fn create_lemon_for(owner: User) {
    Car { make: "AMC", model: "Pacer", owner }
  }
}

let some_user = User {
  active: true,
  coolness_rating: 42,
  name: "Some User",
};

let my_car = Car {
  make: "Mazda",
  model: "Miata",
  owner: some_user,
};

my_car.drive(); // Prints "A Mazda Miata owned by Some User is driving"
}

As structs a big part of the language, Denim has some syntactic sugar to make instantiating nested structs ergonomic.

#![allow(unused)]
fn main() {
let my_other_car = Car {
  make: "Nissan",
  model: "Altima",
  owner: {
    active: false,
    coolness_rating: -1,
    name: "Another User",
  },
};

my_car.drive(); // Prints "A Nissan Altima owned by Another User is driving"
}

One important thing to note here is that Denim structs, like most Denim data structures, are immutable by default. So, direct imperative mutation of Denim structs won't work in all the cases that you may be used to.

#![allow(unused)]
fn main() {
let my_car = Car {
  make: "Mazda",
  model: "Miata",
  owner: some_user,
};

my_car.make = "Toyota"; // Compile-time error.
}

The only way to create a mutable struct instance is to create it with a mut prefixing the struct type. In Denim, structs and traits with a mut are internally mutable.

#![allow(unused)]
fn main() {
let my_mut_car = mut Car {
  make: "Mazda",
  model: "Miata",
  owner: some_user,
};

my_mut_car.make = "Toyota"; // πŸ‘
}

All Denim structs can be shallow cloned with fork. This useful when a field inside an immutable struct value should change.

#![allow(unused)]
fn main() {
let my_car = Car {
  make: "Mazda",
  model: "Miata",
  owner: some_user,
};

let my_other_car = my_car.fork {
  // `make` is changed, but all of the other fields stay the same.
  make: "Toyota",
};

print(my_car.make) // Prints "Mazda"
print(my_other_car.make) // Prints "Toyota"

my_other_car.make = "Toyota"; // Compile-time error (`my_other_car` is not a `mut Car`)
}

But what if you need the forked struct instance to be internally mutable? This is made possible by prefixing the fork expression with a mut.

#![allow(unused)]
fn main() {
let my_car = Car {
  make: "Mazda",
  model: "Miata",
  owner: some_user,
};

let my_other_car = mut my_car.fork;

my_car.make = "Toyota"; // πŸ‘
}

Sometimes you need to fork a struct instance nested within another struct instance. Given how frequently this is necessary, it felt like a good idea for Denim to ship with a dedicated syntax.

#![allow(unused)]
fn main() {
let my_car = Car {
  make: "Mazda",
  model: "Miata",
  owner: some_user,
};

let my_other_car = mut my_other_car.fork {
  make: "Rivian",
  model: "R1T",
  user: fork {
    // `self` refers to the original value of this `User` field.
    coolness_rating: self.coolness_rating + 1,
  },
};
}

In some situations, you may want nested internal mutation: you way want to be able to directly mutate an inner struct instance nested within another struct instance. Denim supports this by declaring the inner struct field as mutable with mut. Note that this nested internal mutability is only accessible in situations where the surrounding type is itself mutable.

#![allow(unused)]
fn main() {
struct House {
  address: Address,
  owner: mut User,
}

struct Address {
  number: string,
  street: string,
}

let a_house = House {
  address: {
    number: "42",
    street: "Wallaby Way",
  },
  owner: {
    coolness_rating: -1,
    name: "P. Sherman",
  },
};

a_house.owner.active = true; // Compile-time error (`a_house` is not a `mut House`)

let another_house = mut House {
  address: {
    number: "221B",
    street: "Baker St",
  },
  owner: {
    coolness_rating: 99,
    name: "S. Holmes",
  },
};

a_house.owner.active = true; // πŸ‘
a_house.address.street = "Baker Street"; // Compile-time error (`House::address` is not an `mut Address`)
}

Control Flow

TODO(skeswa): document branching TODO(skeswa): document loops TODO(skeswa): document loop labels (https://doc.rust-lang.org/rust-by-example/flow_control/loop/nested.html)

"Selfification" (a.k.a Functions as Methods)

Here, we take:

  • "function" to mean a subroutine without a self reference to some piece of state
  • "method" to mean a subroutine with a self reference to some piece of state
#![allow(unused)]
fn main() {
fn authenticate(environment: Environment, auth_user: User) -> bool {
  // ...
}

// `&` is the secret sauce here
fn do_stuff(environment: Environment, user: User?) {
  user.authenticate(environment, &auth_user).if {
    print("yes!");
  } else {
    print("no!");
  };
}
}

"Getterification" (a.k.a Functions as Fields)

In Denim, functions can be treated as fields if they are not passed any arguments.

#![allow(unused)]
fn main() {
struct Foo {
  bar: int,

  pub fn baz(self, extra = 0) -> int {
    self.bar + extra + 1
  }
}

let foo = Foo { bar: 27 };

print(foo.baz(2)); // prints "30"
print(foo.baz()); // prints "28"
print(foo.baz); // prints "28"

}

Autoboxing

Given the prevalance and importance of special enums Option<T> and Result<T> in Denim, you might find yourself wrapping things in Some(..) and Ok(..) alot. To sweeten this syntax a little bit, Denim will automatically infer a T as Some(T) or a Ok(T) depending on context; this is called "autoboxing".

#![allow(unused)]
fn main() {
// Why do this:
fn foo() -> string? {
  Some("bar")
}

// when you could simply do this:
fn autoboxed_foo() -> string? {
  "bar"
}

// `Result` is also autoboxable:
let mut x: Result<int> = Ok(1);
x = 2;

// Autoboxing works in structs too!
struct X(Option<int>);
let x = X(1234);
}

Traits

TODO(skeswa): flesh this out.

#![allow(unused)]
fn main() {
pub trait Summary {
  fn summarize(self) -> string;
}

pub trait Tweak<T> {
  tweakable: T?;

  fn tweak(mut self, some_other_arg: string) -> Self;
}

pub trait DefaultImpl {
  fn foo(mut self) -> string {
    "bar"
  }
}

pub trait MethodGeneric {
  fn some_method<T>(self) -> T;
}
}

Impls

Declaration:

#![allow(unused)]
fn main() {
// non-public
impl B for A {
  fn b(self) {
    print("b");
  }
}

// non-public
impl A for X {
  fn a(self) {
    print("a");
  }
}
impl Y for X {
  fn y(self) {
    print("y");
  }
}
impl Z for X {
  fn z(self) {
    print("z");
  }
}

// non-public
impl X {
  fn new_functionality_for_x(self) {
    print("that newnew");
  }
}

// non-public
pub impl X {
  fn eat(self) {
    print("omnomnom");
  }
}
}

Importing:

#![allow(unused)]
fn main() {
from "~/foo/bar" use {
  B for A,
  Y + Z for X,
  X,
  A,
  X::new_functionality_for_x,
};
}

Generics

TODO(skeswa): flesh this out.

TODO(skeswa): where.

Type Unions and Intersections

TODO(skeswa): flesh this out (Rust Type Unions (trait + trait) and TS Intersections (type | type).

#![allow(unused)]
fn main() {
type IntOrString = int | string;

fn do_a_thing(data: IntOrString) {
  if data is int {
    print("it is an int")
    return;
  }

  print("it is a string")
}

fn do_another_thing(data: int | string | byte) {
  match data {
    is int => print("it is an int"),
    is! string => print("it isn't an string"),
    _ => print("it is probably a string"),
  }
}

trait Foo {
  fn bar(self) -> int,
  fn baz(self) -> bool,
}

trait Ping {
  fn bar(self) -> int,
  fn pong(self),
}

fn do_one_more_thing(data: Foo | Ping) {
  print(data.bar())
}

type FooAndPing = Foo & Ping;

fn do_one_last_thing(data: FooAndPing) {
  data.pong();

  print(data.bar())
}
}

Error handling

You can hoist the error out of returned Result within a function that returns Result with the try keyword.

TODO(skeswa): flesh this out.

#![allow(unused)]
fn main() {
fn do_a_thing() -> Result<void> {
  let csv_file_path = "a/b/c.csv";

  let csv_data = read_file(csv_file_path).try.to_utf8();

  basic_dance_move().context("tried to bust a move").try;
}
}

TODO(skeswa): flesh this out.

#![allow(unused)]
fn main() {
pub struct Error<ErrorKind> {
  pub cause: Error?;
  pub kind: ErrorKind;
}
}

TODO(skeswa): flesh this out.

#![allow(unused)]
fn main() {
panic("Oh no! We cannot recover from this!");
}

Pattern matching

TODO(skeswa): flesh this out.

Concurrency

In Denim, function invocations work differently than in other languages - they do not "block" by default. Instead, Denim functions work like co-routines, suspending themselves while invoking other functions, and resuming when they return.

#![allow(unused)]
fn main() {
// This call to `fetch` blocks, meaning that "done" will not be printed until
// the fetch is finished.
let weather = fetch(url: "https://get.theweather.com");
print("done");

// The code below:
// 1. Prints "a"
// 2. Waits for the first thing to be fetched
// 3. Prints "b"
// 4. Waits for the second thing to be fetched
// 5. Prints "c"

print("a");
let first_thing = fetch(url: "https://first.thing.com");
print("b");
let second_thing = fetch(url: "https://second.thing.com");
print("c");
}

To call a function concurrently, use the async keyword. Using async on a function invocation wraps its return value in a special type called Eventual, like Future or Promise in other languages, is a concurrency monad that allows you to subscribe to the completion of an asynchronous operation.

#![allow(unused)]
fn main() {
// This logic prints "a" then "b" then "c", printing the time in joburg and in
// nyc (in no particular order) when the fetches complete later on.

print("a");
let time_in_joburg = fetch(url: "https://worldtime.com/in/joburg").async;
print("b");
let time_in_nyc = fetch(url: "https://worldtime.com/in/nyc").async;
print("c");

time_in_joburg.then(|time| print("time in joburg: $time"));
time_in_nyc.then(|time| print("time in nyc: $time_in_nyc"));
}

Denim provides a way to "wait" for the Eventual values returned by async functions - the await keyword. With await, working with asynchronous operations can be quite ergonomic.

#![allow(unused)]
fn main() {
// This logic:
// 1. Starts fetching the time in atlanta
// 2. Prints "a"
// 3. Waits for the time in atlanta to be available before printing it
// 4. Prints "b"

let time_in_atlanta = fetch(url: "https://worldtime.com/in/atlanta").async;

print("a");
print(time_in_atlanta.await);
print("b");
}

There are a garden variety of helpful ways to wait on multiple asynchronous operations at once in Denim. In particular, tuples are a great way to wait on Eventual values with different types.

#![allow(unused)]
fn main() {
// Any tuple of `Eventual` values has a method called `.after_all()` that waits
// for every `Eventual` to complete.
let (google_icon, _, c_text) = (
  fetch(url: "https://google.com/favicon.ico").async,
  pause(Duration::new(milliseconds: 100)).async,
  readFile(path: "./a/b/c.txt").async,
).after_all().await;

// Arrays of `Eventual` also support `.after_all()`.
let icons = [
  fetch(url: "https://nba.com/favicon.ico").async,
  fetch(url: "https://nhl.com/favicon.ico").async,
  fetch(url: "https://mlb.com/favicon.ico").async,
].after_all().await;
}

Since this is quite a common pattern in Denim, there is a special syntax for it enabled by the tandem keyword. tandem { ... } is simply syntactic sugar for the concurrent invocation of multiple potentially asynchronous functions normally enabled by the combined usage of .async and .after_all().await. As such, a tandem { ... } expression evaluates to a tuple of the return values of each comma delimited sub-expression.

#![allow(unused)]
fn main() {
let (google_icon, _, c_text) = tandem {
  fetch(url: "https://google.com/favicon.ico"),
  pause(Duration::new(milliseconds: 100)),
  readFile(path: "./a/b/c.txt"),
};

let icons = tandem {
  fetch(url: "https://nba.com/favicon.ico"),
  fetch(url: "https://nhl.com/favicon.ico"),
  fetch(url: "https://mlb.com/favicon.ico"),
};
}

Both tuples and arrays of Eventual support .race() to wait for the first Eventual to complete, wrapping values in Option::None to mark Eventual values that lost the race.

#![allow(unused)]
fn main() {
let results = [
  fetch(url: "https://later.com").async,
  fetch(url: "https://now.com").async,
  fetch(url: "https://way.later.com").async,
].race().await;

print("$results"); // Prints "[None, Some(data), None]"
}

Interop

TODO(skeswa): flesh this out.

#![allow(unused)]
fn main() {
trait Pluralizer {
  fn pluralize(self, text: string) -> string;
}

extern(go) {
  // Automatically compiles a Denim-friendly interface.
  from(go) "github.com/gertd/go-pluralize@0.2" use { ...pluralize };

  struct GoPluralizer {
    client: pluralize.Client,
  }

  impl Pluralizer for GoPluralizer {
    fn pluralize(self, text: string) -> string {
      self.client.plural(text)
    }
  }

  pub fn new_pluralizer() -> impl Pluralizer {
    GoPluralizer { client: pluralize.NewClient() }
  }
}

extern(ts) {
  #[deno_types("npm:@types/pluralize@0.0.29")]
  from(ts) "npm:pluralize@8.0.0" use { pluralize };

  struct TsPluralizer {}

  impl Pluralizer for TsPluralizer {
    fn pluralize(self, text: string) -> string {
      pluralize(text)
    }
  }

  pub fn new_pluralizer() -> impl Pluralizer {
    TsPluralizer {}
  }
}
}

Testing

TODO(skeswa): flesh this out.

In any "*.(spec|test).denim" file (spec = unit test, test = integeration test):

describe, before, test are all functions that only apply to tests.

#![allow(unused)]
fn main() {
describe("Something") {
  before {

  }

  test("Something") {

  }

  #[timeout(seconds: 500)]
  test("Something") {

  }

  if !is_dev {
    test("Something") {

    }
  }
}
}