Article

Introducing the Ferrocene Language Specification

Published on 4 min read
Ferrocene icon
Ferrocene
A Rust compiler toolchain for safety- and mission-critical environments.

    Announcement

    At Ferrous Systems, our work to qualify rustc to produce Ferrocene is primarily an exercise in documentation. And today, we're excited to give you a glimpse into the core of that package: the Ferrocene Language Specification (FLS).

    This work is a key part of our partnership with AdaCore – who are experienced in preparing software documentation for safety-certified qualification. As experts in Rust, we're applying the knowledge and they're providing the structure. By working together and with the help of the existing Rust language documentation – we're creating a qualification-oriented document that details the Rust language in so far as its relevant to Ferrocene.

    AdaCore's experience writing and maintaining the Ada Reference Manual has been crucial, and it inspired the methodology, structure and level of detail we're using to write the FLS.

    This is by no means an effort to rewrite or change Rust to adapt it to the structure of the Ada language, but rather using the methodology behind documenting Ada. The FLS is documentation, reactive to the Rust project and still determined by the Rust project's changes, decisions and RFCs, but in a documentation form that meets the requirement for qualification in safety-critical environments.

    Each element of the Rust language is described by its Syntax, Legality Rules (which describes the expected behavior of the language), its Dynamic Semantics and some examples. Where relevant, we also include Undefined Behavior.

    It should be noted that this is not an effort to document the entire Rust language and standard library. Ferrocene is the effort to qualify a version of the Rust compiler, which means we can also be selective when it comes to what subset gets documented.

    We know there is high interest in the Rust community about the need for a thorough writedown of the Rust compilers behavior. If you or your teams are currently investigating this kind of effort, reach out to us at ferrocene-spec@ferrous-systems.com.

    In the end, this effort will not stay proprietary. The goal – aside from qualifying Ferrocene – is to create something that anyone can reference as they use Rust. To achieve that we'll be making the FLS publicly available under Rust's standard licenses (MIT or Apache 2.0, at the user's discretion), and we aim to publish an initial draft in Summer 2022.

    To make the structure of the specification more concrete, we'd like to end this blog post with an example. In the current draft of the FLS, we describe Call Expressions as such:

    Call Expressions (sample)

    Syntax

    CallExpression ::=
        CallOperand ( ArgumentOperandList? )
    
    CallOperand ::=
        Operand
    
    ArgumentOperandList ::=
        ExpressionList
    

    Legality Rules

    The operand of a call expression is referred to as the call operand. The operands within the ArgumentOperandList are referred to as the argument operands.

    The call operand shall be subject to auto dereferencing until either a function item type, a function ptr type or a type that implements any of the core::ops::Fn, core::ops::FnMut, or core::ops::FnOnce traits has been found. This type is referred to as the callee type.

    The adjusted call operand is the call_operand with the auto dereference adjustments of callee type applied.

    The type of a call expression is the return type of the invoked function or the associated type core::ops::FnOnce::Output.

    A call expression whose callee type is of a function item type or a function ptr type with an unsafe qualifier shall require unsafe context.

    A call expression whose callee type is of an external function item type shall require unsafe context.

    The value of a call expression is determined as follows:

    • If the callee type is a function item type or a function pointer type, then the value is the result of invoking the corresponding function with the argument operands.
    • If the callee type implements the core::ops::Fn trait, then the value is the result of invoking core::ops::Fn::call(adjusted_call_operand, argument_operand_tuple), where adjusted_call_operand is the adjusted call operand, and argument_operand_tuple is a tuple that wraps the argument operands.
    • If the call operand implements the core::ops::FnMut trait, then the value is the result of invoking core::ops::FnMut::call_mut(adjusted_call_operand, argument_operand_tuple) where adjusted_call_operand is the adjusted call operand, and argument_operand_tuple is a tuple that wraps the argument operands.
    • If the call operand implements the core::ops::FnOnce trait, then the value is the result of invoking core::ops::FnOnce::call_once(adjusted_call_operand, argument_operand_tuple) where adjusted_call_operand is the adjusted call operand, and argument_operand_tuple is a tuple that wraps the argument operands.

    Dynamic Semantics

    The evaluation of a call expression proceeds as follows:

    1. The call operand is evaluated.
    2. The argument operands are evaluated in left-to-right order.
    3. If the adjusted call operand is a function item type or function pointer type, then corresponding function is invoked.
    4. If the adjusted call operand implements the core::ops::Fn trait, then core::ops::Fn::call(adjusted_call_operand, argument_operands) is invoked.
    5. If the adjusted call operand implements the core::ops::FnMut trait, then core::ops::FnMut::call_mut(adjusted_call_operand, argument_operands) is invoked.
    6. If the adjusted call operand implements the core::ops::FnOnce trait, then core::ops::FnOnce::call_once(adjusted_call_operand, argument_operands) is invoked.

    Undefined Behavior

    It is undefined behavior to call a function with an ABI other than the ABI the function was defined with.

    Examples

    let three: i32 = add(1, 2);