Writing CloudABI applications in Rust
Installing a toolchain
Installing a toolchain for Rust is very easy, as support for CloudABI has been upstreamed into the Rust codebase. Automated builds are performed by the Rust developers. As there hasn’t been a stable release of Rust to include CloudABI support yet, you must for now make use of Rust’s nightly track.
A commonly used method for installing the Rust compiler is to use rustup. If rustup is not available through your system’s package manager, it can be installed in your home directory by running the official installation shell script:
curl https://sh.rustup.rs -sSf | sh
Once rustup is installed, the following commands can be used download a copy of the Rust standard libraries for CloudABI:
rustup toolchain install nightly
rustup default nightly
rustup target add x86_64-unknown-cloudabi
Rust depends on cloudlibc for
memory allocation and threading. It also makes use of LLVM’s libunwind
to provide backtraces upon panic!()
. You must therefore
install a toolchain for C and C++ to be able to link actual
binaries.
Building a simple application
Assuming that both a C++ and Rust toolchain are installed, it is
possible to build Rust applications for CloudABI by making use of
Cargo’s --target
flag:
$ cat Cargo.toml
[package]
name = "hello_world"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
$ cat src/main.rs
fn main() {
println!("Hello, world!");
}
$ cargo build --target=x86_64-unknown-cloudabi
The resulting executable can be started with cloudabi-run
as follows:
cloudabi-run target/x86_64-unknown-cloudabi/debug/hello_world < /dev/null
This will, however, not generate any output, for the reason that an
output sink (stdout
) needs to be provided in the YAML configuration
explicitly.
Accessing Argdata
The code below shows how our “Hello, world” application can be extended
to actually work. The argdata
crate provides access to the arguments
passed to the process. The cloudabi
create contains bindings against
the CloudABI system call interface. We use it to implement an object
with trait std::io::Write
.
In the long run, we want to work towards simplifying this code
dramatically. There should be no need to implement the Console
object
yourself.
$ cat Cargo.toml
[package]
name = "hello_world"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
[dependencies]
argdata = "*"
cloudabi = "*"
$ cat src/main.rs
#![feature(set_stdio)]
extern crate argdata;
extern crate cloudabi;
use argdata::Argdata;
use argdata::ArgdataExt;
pub struct Console(cloudabi::fd);
impl std::io::Write for Console {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
unsafe {
let iovs = [
cloudabi::ciovec {
buf: (buf.as_ptr() as *const _ as *const (), buf.len()),
},
];
let mut nwritten = std::mem::uninitialized();
cloudabi::fd_write(self.0, &iovs, &mut nwritten);
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
fn main() {
let ad = argdata::env::argdata();
let mut it = ad.read_map().expect("argdata should be a map").iter_map();
while let Some(Ok((key, val))) = it.next() {
match key.read_str().expect("keys should be strings") {
"console" => {
let fd = val.read_fd().expect("console should be a file descriptor");
std::io::set_print(Some(Box::new(Console(cloudabi::fd(fd.0 as u32)))));
std::io::set_panic(Some(Box::new(Console(cloudabi::fd(fd.0 as u32)))));
}
_ => {}
}
}
println!("Hello, world!");
}
$ cargo build --target=x86_64-unknown-cloudabi
$ cloudabi-run target/x86_64-unknown-cloudabi/debug/hello_world << EOF
%TAG ! tag:nuxi.nl,2015:cloudabi/
---
console: !fd stdout
EOF
Hello, world!