Blocks were briefly introduced in the function chapter. They provide a modular way to package a function's logic.

There are several kinds of blocks, each with a different approach to supporting these key roles:

Blocks offer a versatile, comprehensible way to structure logic. Their well-defined single point of entry and exit simplifies both control flow and context management. Versatility comes from being able to nest different kinds of block easily within each other.

Ordered Collection of Statements

At its simplest, a block is merely a ordered set of statements. You can tell which statements are part of the same block, as they are indented more than the statements that come before and after the block. This indentation typically consists of spaces, although tabs are supported if used consistently. Alternatively, curly braces may be used (rather than indented lines) to enclose a block's statements.

Each statement typically has its own line. However, multiple statements may be placed on the same line, separated with semicolons. Use of the backslash continuation character can be used whenever a line needs to span multiple lines. The detailed styling rules are explained here.

A statement can be:

Blocks and Control Flow

Cone supports many different kinds of blocks. From a control flow perspective, these can be categorized as:

Local variables and the lexical context

A block may carry its own state information in the form of local, named variables. This block state is transient; space for it is effectively allocated when execution of the block begins. When execution of the block ends (by any means), appropriate clean up activity is triggered (where defined) and then the state information disappears.

Local variables are declared exactly the same way as described for global variables. They are known to be local simply because they are declared within a function's block.

fn summult(a f32, b f32) f32
    imm sum, mult = a + b, a * b  // local variable declaration
    return sum / mult

Once a local variable has been declared, it may be used anywhere after in the same (or inner) block. However, if an inner block declares a local variable of the same name (allowed but not always good practice), the outer scoped variable is effectively invisible throughout the lexical scope of that inner block.

Blocks use a number of mechanisms under the covers to ensure that resources attached to local variables are handled safely. This is particularly true when references are involved. Such mechanisms enforce aliasing and shared use permission constraints and trigger appropriate allocator de-aliasing events. Further details are covered in the reference chapters.

Building and/or returning a block value

In many cases (depending on the kind of block), a block can be used as an expression.

a =
    6      // a's value will become 6

The value of an expression block is the value of its last expression statement, in effect working exactly the same way as a function's implicit return. As you will see later, the same principle applies to if and match control blocks.

Additionally, a build or this block may be used to focus on and build a value, which is a particularly helpful and readable way to assemble containers.

Enforcing safety rules

The compiler comes with built-in guard rails to help protect your code against a number of safety hazards, which is a good thing. However, these safety guards are extremely cautious and not always very clever. They stop your code from doing things that, under the worst of all possible conditions, could cause memory or race conditions. Sometimes this means they stop your code from doing things that you want to do, and which you can ensure by other means is perfectly safe, but the compiler just does not know how to verify. The compiler is trying to be helpful, but is just getting in the way.

In such situations, Cone provides a built-in escape hatch from the guard rails, in the form of an annotation. By preceding any block with trust (as in: "trust me on this"), several safety checks are turned off, thereby allowing:

Safety is enriched when the programmer and the compiler work together towards that end, each bringing different gifts to bear on the problem. It would not be fair to imply that the compiler's inability to verify safety means that the code is inherently unsafe. By using this "trust" mechanism, compiler-unverifiable code can be isolated and highlighted as such, while still acknowledging that the programmer, who has the ultimate responsibility for safety, may very clearly see what the compiler cannot.