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.

Such variables are local to a block simply by being declared within a block:

fn a_function()
	mut loco f32
	while true
		mut x i32  // indentation shows x's lifetime is bounded by while block
	x // Error!! x is not accessible here outside the while block

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.

The lifetime of a local variable is bounded by the inner-most lexical block the variable is declared within. (Additional indentation of lines typically signals entrance into a new inner block.) The variable is unknown outside its declared scope; any attempt to access it outside its lexical block will fail.

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.

Block Variants

There are several helpful variations on the basic, single use block.

do blocks

When using indentation to signify the start of a new block, use 'do' in the unindented line before indentation begins:

imm x = 3
x += 1
// Start a new block here
	mut x = 6
// Old block resumes here

Use of 'do' subblocks is typically only needed when one wants to diminish the lifetime of variables or borrowed references. Specifying 'do' is needed to distinguish a simple block from a 'this' block.

'this' and its block

In conversations, we often use pronouns like "she" and "this" as a convenient way to refer to some specific person or thing we are talking about. Cone uses the pseudo-variable this for the same purpose. It allows a block of code to focus its attention on some specified value referred to by this.

A 'this' block is simply an expression followed by a code block. Throughout the block, this is understood to hold the value of the specified expression. Multiple actions may be performed using that value.

// Calculate the value of 'this' (0.174533)
	// 'this' block that makes use of calculated 'this' value
	// to convert from degrees to radians
	quarter = 90 * this
	acute = 10 * this

'this' blocks can be nested within each other. For any statement, the value of this is established by the inner-most 'this' block it lies within.

Although a 'this' block can focus on any value, it is typically a copyable or borrowed mutable reference to some compound or collection value whose parts we want to read or modify or whose methods we want to invoke over a sequence of statements. 'this' blocks offer an easy-to-read and more versatile alternative to method chaining or method cascades.

Operator support for implicit 'this'

To make use of this more convenient and concise, several operators implicitly work with this. When these operator are used as a prefix (rather than as infix operators), this is implied to the left.

Method call or property access. (.size is equivalent to this.size)
Append, placing the value at the end of an ordered collection. (<<4.7 is equivalent to this<<4.7)
Prepend, inserting the value at the start of an ordered collection. (>>event is equivalent to this>>event)

Additionally, the ':' infix operator implicitly works with 'this':

color: 'brown'    # equivalent to: this.color = 'brown'

So far, 'this' blocks and operators sound rather dull and boring... What value does this syntactic sugar offer? Let's finish with examples that illustrate how use of 'this' sugar makes code more concise and readable.

Method chaining/cascade:

// Perform several methods on on root
	.add(anode)                    // Unlike chaining, the method need not return self
	angle = 20 * .degrees          // Unlike cascades, any logic can be placed between method calls
	.add(node) each node in nodes  // ... even within conditional branches and loops

Formatted stream output or string creation:

	<< "Score: "       // Append to console stream
	color: blue        // Alter a property
	<< score           // Append with auto-conversion
	color: white
	<< "!\n"
	.flush             // Invoke methods

console << {"And another thing ";name;"!\n"}

List construction (or expansion):

// Use of << at the start means append all the block's expression values
names = &own List() <<

Dictionary construction:

toDutch = &gc Dict{Text, Text}()
	"girl": "meisje:
	"horse": :paard"
	"dog": "hond"

Using blocks

Trust blocks: Relaxing Safety Checks

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.