Evitare l'uso di stdlib

La libreria standard di Rust fornisce molte utili funzionalità, ma assume che il suo sistema ospitante supporti vari caratteristiche: i thread, la rete, l'allocazione dinamica di memoria, e altro. Però, ci sono dei sistemi che non hanno queste caratteristiche, e Rust può produrre del software anche per loro! Per farlo, bisogna dire a Rust che non si vuole usare la libreria standard, usando l'attributo: #![no_std].

Nota: Questa caratteristica è tecnicamente stabile, ma ci sono dei cavilli. Per dirne one, si può costruire una libreria con #![no_std] con la versione stabile, ma non un programma. Per avere dettagli sulle librerie senza la libreria standard, si veda il capitolo su #![no_std]

Ovviamente nella vita c'è di più che le librerie: si può usare #[no_std] anche con un programma.

Usare "libc"

Per poter costruire un programma #[no_std], avremo bisogno di una dipendenza da libc. Lo possiamo specificare usando il nostro file Cargo.toml:

[dependencies]
libc = { version = "0.2.14", default-features = false }

Si noti che le caratteristiche di default ["default features"] sono state disabilitate. Questo è un passo critico - le caratteristiche di default di libc comprendono la libreria standard e quindi devono essere disabilitate.

Scrivere un programma senza stdlib

Si può controllare il punto di ingresso in due modi: o usando l'attributo #[start], o sovrascrivendo con una propria funzione la routine di default che richiama la funzione main del linguaggio C.

La funzione marcata #[start] riceve gli argomenti della riga di comando nello stesso formato del linguaggio C:

#![feature(lang_items)] #![feature(start)] fn main() { #![no_std] // Incorpora la libreria libc di sistema per avere ciò che probabilmente // e' richiesto da crt0.o extern crate libc; // Punto d'ingresso in questo programma #[start] fn start(_argc: isize, _argv: *const *const u8) -> isize { 0 } // Queste funzioni sono usate dal compilatore, ma non // per uno scheletrico "hello world". Queste sono normalmente // fornite da libstd. #[lang = "eh_personality"] #[no_mangle] pub extern fn eh_personality() { } #[lang = "panic_fmt"] #[no_mangle] pub extern fn rust_begin_panic( _msg: core::fmt::Arguments, _file: &'static str, _line: u32) -> ! { loop {} } }
#![feature(lang_items)]
#![feature(start)]
#![no_std]

// Incorpora la libreria libc di sistema per avere ciò che probabilmente
// e' richiesto da crt0.o
extern crate libc;

// Punto d'ingresso in questo programma
#[start]
fn start(_argc: isize, _argv: *const *const u8) -> isize {
    0
}

// Queste funzioni sono usate dal compilatore, ma non
// per uno scheletrico "hello world". Queste sono normalmente
// fornite da libstd.
#[lang = "eh_personality"]
#[no_mangle]
pub extern fn eh_personality() {
}

#[lang = "panic_fmt"]
#[no_mangle]
pub extern fn rust_begin_panic(
    _msg: core::fmt::Arguments,
    _file: &'static str,
    _line: u32) -> ! {
    loop {}
}

Per scavalcare lo shim main inserito dal compilatore, lo si deve disabilitare scrivendo #![no_main] e poi si deve creare l'appropriato simbolo con l'ABI corretto il nome corretto, che richiede lo scavalcamento anche della storpiatura ["mangling"] del nome da parte del compilatore:

#![feature(lang_items)] #![feature(start)] #![no_std] #![no_main] // Tira dentro la libreria libc di sistema per avere ciò che probabilmente // e' richiesto da crt0.o extern crate libc; // Punto d'ingresso in questo programma #[no_mangle] // assicurati che questo simbolo sia chiamato `main` nell'output pub extern fn main(_argc: i32, _argv: *const *const u8) -> i32 { 0 } // Queste funczioni e questi tratti sono usati dal compilatore, ma non // per uno scheletrico "ciao mondo". Normalmente sono // forniti da libstd. #[lang = "eh_personality"] #[no_mangle] pub extern fn eh_personality() { } #[lang = "panic_fmt"] #[no_mangle] pub extern fn rust_begin_panic( _msg: core::fmt::Arguments, _file: &'static str, _line: u32) -> ! { loop {} }
#![feature(lang_items)]
#![feature(start)]
#![no_std]
#![no_main]

// Tira dentro la libreria libc di sistema per avere ciò che probabilmente
// e' richiesto da crt0.o
extern crate libc;

// Punto d'ingresso in questo programma
#[no_mangle] // assicurati che questo simbolo sia chiamato `main` nell'output
pub extern fn main(_argc: i32, _argv: *const *const u8) -> i32 {
    0
}

// Queste funczioni e questi tratti sono usati dal compilatore, ma non
// per uno scheletrico "ciao mondo". Normalmente sono
// forniti da libstd.
#[lang = "eh_personality"]
#[no_mangle]
pub extern fn eh_personality() {
}

#[lang = "panic_fmt"]
#[no_mangle]
pub extern fn rust_begin_panic(
    _msg: core::fmt::Arguments,
    _file: &'static str,
    _line: u32) -> ! {
    loop {}
}

Altro sui "language item"

Il compilatore attualmente fa alcune assunzioni sui simboli che possono essere chiamati nel programma. Normalmente queste funzioni sono fornite dalla libreria standard, ma senza di essa bisogna definire i propri. Questi simboli sono chiamati "language item" ("voci del linguaggio"), e ognuno di essi ha un nome interno, e poi hanno una firma a cui ogni implementazione si deve conformare.

La prima di queste due funzioni, eh_personality, è usata dai meccanismo di fallimento del compilatore. Questa è spesso mappata alla funzione di personalità del GCC (si veda l'implementazione di libstd per avere altre informazioni), ma i crate che non fanno scattare un panico possono stare tranquilli che questa funzione non verrà mai chiamata. Sia i language item che i nomi di simboli sono eh_personality.

La seconda funzione, panic_fmt, viene pure usata dai meccanismi di fallimento del compilatore. Quando avviene un panico, questo controlla il messaggio che viene visualizzato sullo schermo. Mentre il nome del language item è panic_fmt, il nome del simbolo è rust_begin_panic.