AdaCore Blog

Adding Ada to Rust

Adding Ada to Rust

by Johannes Kliemann

While writ­ing every part of a soft­ware project in Ada or SPARK may pro­vide the best safe­ty and secu­ri­ty, doing so requires sig­nif­i­cant effort and is not fea­si­ble for many projects. As we demon­strat­ed in a pre­vi­ous blog post, one can use a C Soft­ware Devel­op­ment Kit (SDK)) to pro­vide the base func­tion­al­i­ty of a project and only imple­ment the appli­ca­tion log­ic in Ada or SPARK.

While imple­ment­ing the appli­ca­tion log­ic in Ada or SPARK is an improve­ment over a pure C project, its weak­est link is still the C code in the SDK. On the oth­er hand there are many libraries, board sup­port pack­ages and SDKs writ­ten in Rust, eas­i­ly usable with Car­go. So instead of build­ing the Ada appli­ca­tion on top of a C base, one could use a Rust base instead to com­bine the large cat­a­log of ready-to-use soft­ware with Rust’s safe­ty fea­tures, pro­vid­ing a much more sol­id base for an Ada project.

Using Car­go with exter­nal build sys­tems #

The Rusts build sys­tem, Car­go, only sup­ports Rust by default. How­ev­er, Car­go allows the user to pro­vide a build script called build​.rs that can do any desired task, be it set­ting up a project direc­to­ry or just call­ing anoth­er build sys­tem. Fur­ther­more, Car­go allows pass­ing cus­tom flags to the Rust com­pil­er and link­er, enabling one to link with cus­tom dynam­ic libraries:

println!(“cargo:rustc-link-lib=dylib=mycustomadalib”);

Thus, one can use Car­go to build an Ada/​Rust pro­gram by com­pil­ing the Ada code out­side of Car­go as a library and then man­u­al­ly link­ing the Ada library using the above line in our project’s build​.rs.

How­ev­er, hav­ing to build the Ada library man­u­al­ly every time is cum­ber­some. So we can add the Ada build step to build​.rs too:

Command::new(“gprbuild”)
    .args([“-j0”, “-p”, “-P”, “mycustomadalib.gpr”)
    .spawn()
    .unwrap()
    .wait()
    .unwrap()

Using the Library_Dir, Library_Kind and Library_Name attrib­ut­es defined in our project’s GPR file, we can link to the Ada library from Rust:

println!(“cargo:rustc-link-lib=dylib=mycustomadalib”);
println!(“cargo:rustc-link-search=/path/to/mycustomadalib_dir”);

While this does not look too dif­fi­cult, it may become cum­ber­some if mul­ti­ple projects are involved or if the project files gen­er­ate dif­fer­ent library names, kinds and direc­to­ries depend­ing on the envi­ron­ment or the passed options. Since the project file con­tains this infor­ma­tion, why not direct­ly take it from there?

Build­ing Ada projects with the gpr crate #

To make things eas­i­er, I cre­at­ed the gpr crate in Rust. It takes libgpr2 and pro­vides bind­ings for use in Car­go build scripts. It will load and parse the select­ed project file and pro­vide the required infor­ma­tion to build and link the Ada library, such as the default para­me­ters for GPRbuild and the library paths, names and kinds.

Depen­den­cies #

The gpr crate itself only requires alire to build itself. It does not pro­vide a tool­chain for the Ada project it is build­ing so that must be pro­vid­ed separately.

Usage #

Using gpr is quite sim­ple. The only require­ment is to pro­vide the path to the project file. The project file will pro­vide all the infor­ma­tion required for Rust to link to the library.

use gpr;

let project = gpr::Project::load(Path::new("/path/to/project.gpr")).unwrap();

Command::new("gprbuild")
    .args(project.gprbuild_args().unwrap())
    .spawn()
    .unwrap()
    .wait()
    .unwrap();

println!(
    "cargo:rustc-link-search{}",
    project.library_dir().unwrap().to_str().unwrap()
);
println!(
    "cargo:rustc-link-lib={}={}",
    project.library_kind().unwrap(),
    project.library_name().unwrap()
);

Build­ing an exam­ple appli­ca­tion #

To demon­strate the use of the gpr crate, let’s cre­ate a sim­ple appli­ca­tion in Rust that will print Hel­lo World” from both lan­guages. To start, add the gpr crate as a build dependency:

[package]
name = "ada_hello"
version = "0.1.0"
edition = "2021"

[build-dependencies]
gpr = “0.1.0”

The imple­men­ta­tion of the Ada library is straightforward,containing a sin­gle pro­ce­dure that prints Hel­lo World”. We build the library as a dynam­ic library:

project Ada_Hello
is

   for Source_Dirs use ("src");
   for Object_Dir use "obj";
   for Create_Missing_Dirs use "True";
   for Library_Name use "adahello";
   for Library_Kind use "dynamic";
   for Library_Standalone use "encapsulated";
   for Library_Interface use ("ada_hello");
   for Library_Dir use "lib";

end Ada_Hello;

package Ada_Hello
is

   procedure Hello with
      Export,
      Convention => C,
      External_Name => "ada_hello";

end Ada_Hello;

with Ada.Text_IO;

package body Ada_Hello is

   procedure Hello
   is
   begin
      Ada.Text_IO.Put_Line ("Hello from Ada!");
   end Hello;

end Ada_Hello;

A sim­ple build script is required to build and link this library:

use gpr::Project;
use std::{path::Path, process::Command};

fn main() {
    let ada_hello = Project::load(Path::new("ada_hello/ada_hello.gpr")).unwrap();
    Command::new("gprbuild")
        .args(ada_hello.gprbuild_args().unwrap())
        .spawn()
        .unwrap()
        .wait()
        .unwrap();
    println!(
        "cargo:rustc-link-search={}",
        ada_hello.library_dir().unwrap().to_str().unwrap()
    );
    println!(
        "cargo:rustc-link-lib={}={}",
        ada_hello.library_kind().unwrap(),
        ada_hello.library_name().unwrap()
    );
}

And final­ly, the Rust main pro­gram that calls the Ada application:

extern "C" {
    fn ada_hello();
}

fn main() {
    println!("Hello from Rust!");
    unsafe {
        ada_hello();
    }
}

Now the project is ready to be com­piled and run. Since the Ada library is dynam­ic, the LD_LIBRARY_PATH envi­ron­ment vari­able needs set­ting before run­ning the exam­ple, as it would oth­er­wise not find the library at runtime:

LD_LIBRARY_PATH=ada_hello/lib cargo run        
   Compiling gpr v0.1.0 (/.../gpr-rust)
   Compiling ada_hello v0.1.0 (/.../gpr-rust/examples/ada_hello)
    Finished dev [unoptimized + debuginfo] target(s) in 6.02s
     Running `target/debug/ada_hello`
Hello from Rust!
Hello from Ada!

The gpr crate so far pro­vides the basic func­tion­al­i­ty to build shared and sta­t­ic Ada libraries. If you find a bug or think a fea­ture is miss­ing, please open an issue at https://​github​.com/​j​k​l​m​n​n​/​g​p​r​-rust.

Posted in

About Johannes Kliemann

Johannes Kliemann

Johannes Kliemann is a cross and embedded engineer at AdaCore. He specializes in systems architecture and security and is interested in formal verification, operating systems development and embedded devices.