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 invokingcore::ops::Fn::call(adjusted_call_operand, argument_operand_tuple)
, whereadjusted_call_operand
is the adjusted call operand, andargument_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 invokingcore::ops::FnMut::call_mut(adjusted_call_operand, argument_operand_tuple)
whereadjusted_call_operand
is the adjusted call operand, andargument_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 invokingcore::ops::FnOnce::call_once(adjusted_call_operand, argument_operand_tuple)
whereadjusted_call_operand
is the adjusted call operand, andargument_operand_tuple
is a tuple that wraps the argument operands.
Dynamic Semantics
The evaluation of a call expression proceeds as follows:
- The call operand is evaluated.
- The argument operands are evaluated in left-to-right order.
- If the adjusted call operand is a function item type or function pointer type, then corresponding function is invoked.
- If the adjusted call operand implements the
core::ops::Fn
trait, thencore::ops::Fn::call(adjusted_call_operand, argument_operands)
is invoked. - If the adjusted call operand implements the
core::ops::FnMut
trait, thencore::ops::FnMut::call_mut(adjusted_call_operand, argument_operands)
is invoked. - If the adjusted call operand implements the
core::ops::FnOnce
trait, thencore::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);