Records
You're just doing great! Now, let us introduce record types, often called "structs" in other languages.
Project Setup
Let's clear our ./src/lib.rs
and start from scratch
Introduction
Record is a combination of data that structurally belongs together such as:
Syntax for a record
record-item ::= 'record' id '{' record-fields '}'
record-fields ::= record-field | record-field ',' record-fields?
record-field ::= id ':' ty
A Record lets you pass data between the guests and the hosts
The WIT File
Now let us define our WIT file for our interface.
//geometry.wai
/// A point coordinate structure with { x, y }
record point {
x: float32,
y: float32
}
Note: As you use
cargo expand
, the generated file won't contain thePoint
Geometry ๐
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
#[allow(clippy::all)]
mod geometry {}
This happens because we didn't use our
Point
geometry in any function/interface so it is not compiled to an underlying struct forRust
.
So now let's use our Point
geometry in a function.
// geometry.wai
/// A point coordinate structure with { x, y }
record point {
x: float32,
y: float32
}
/// Calculate distance between two points
distance-between: func(p1: point, p2: point) -> float32
cargo expand
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
use crate::geometry::{Circle, MultiLine, Point};
#[allow(clippy::all)]
mod geometry {
/// A point coordinate structure with { x, y }
#[repr(C)]
pub struct Point {
pub x: f32,
pub y: f32,
}
#[automatically_derived]
impl ::core::marker::Copy for Point {}
#[automatically_derived]
impl ::core::clone::Clone for Point {
#[inline]
fn clone(&self) -> Point {
let _: ::core::clone::AssertParamIsClone<f32>;
*self
}
}
impl core::fmt::Debug for Point {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Point").field("x", &self.x).field("y", &self.y).finish()
}
}
#[export_name = "distance-between"]
unsafe extern "C" fn __wai_bindgen_geometry_distance_between(
arg0: f32,
arg1: f32,
arg2: f32,
arg3: f32,
) -> f32 {
let result = <super::Geometry as Geometry>::distance_between(
Point { x: arg0, y: arg1 },
Point { x: arg2, y: arg3 },
);
wai_bindgen_rust::rt::as_f32(result)
}
pub trait Geometry {
/// Calculate distance between two points
fn distance_between(p1: Point, p2: Point) -> f32;
}
}
Here, we see that our
Point
struct can be seendistance_between
function is present and is exported as an external variable for use in our Rust file. We also see the Debug trait being implemented for the Point record.
This file has a record and a function. The record is a structure for a point in a coordinate system (x,y). The function performs the distance calculation between two points as arguements.
Writing Some Rust
Now we've defined our geometry.wai
file, let's implement the crate.
The first thing we need to do is add wai-bindgen
as a dependency
$ cargo add wai-bindgen-rust
We also need to tell wai-bindgen
that we're implementing
geometry.wai
.
#![allow(unused)] fn main() { // src/lib.rs wai_bindgen_rust::export!("geometry.wai"); }
Next, we need to define a geometry
struct and implement the
geometry::Geometry
Trait on it.
#![allow(unused)] fn main() { struct Geometry; impl geometry::Geometry for Geometry { fn distance_between(p1: Point, p2: Point) -> f32 { let Point { x: x1, y: y1 } = p1; let Point { x: x2, y: y2 } = p2; ((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt() } } }
Note: This may seem comfusing so I've boiled it down:
geometry
is the crateGeometry
is the structgeometry::Geometry
is the Trait that implements the functiondistance_between
onGeometry
Explaination
Here, the function distance_between
takes two arguement of the Point type. For simplicity we destructure it for a clear distinction between the x1, x2 and y1,y2 as opposed to writing p1.x
or p1.y
everytime.
We then find the distance between the two points using the distance formula.
Note๐
As
.wai
files only accept kebab-casing. The functiondistance_between
in thegeometry.wai
will convert to the default casings for the respected language.//change here after formatting!! i.e: snake_case for rust, CamelCase for Javascript,
Nested Records
As we saw, the use of simpler identifiers to create a Point
record. we can further extend this functionality using records or other valid WAI types
to specify the record arguments to create more complex and nested records.
โ ๏ธ Recursive types are explicitly forbidden in WAI.
record tree-node {
children: list<tree-node>
}
๐๐ผ, is not allowed.
Let's futher explain Nested Records
this with an example:
WAI file with nested records :
/// A line geometry to represent a line segment with starting and ending point
record line-segment {
start: point,
end: point
}
/// A structure to represent a circle with center point and a radius
record circle {
center: point,
radius: float32
}
/// Arbitary shape - represent a shape with n number of points using a list of points
record multi-line{
points: list<point>,
}
/// Calculate the perimeter of a Circle using 2*ฯ*r.
perimeter-of-circle: func(c: circle) -> float32
/// Calculate the area of a Circle using ฯ*r*r.
area-of-circle: func(c: circle) -> float32
/// Calculate the length of the multi-line by iterating over all the points and adding the result
multi-line-length: func(l: multi-line) -> float32
Here we used the point
struct that we created earlier to futher define records (i.e. line-segment
, circle
and shape
).
- line segment uses points to define starting and ending of the line
- Circle uses the point record for defining a center
- An Arbitrary shape can also be represented as a list of points
If we had x,y for representing points in each of these geometries it would have no structure and code readability. Thus, we define nested records using a previously existing record.
Note๐
Records can further have type identifiers such as u8, u16, float32, enum, tuple, etc.
Writing Some Rust Again
use crate::geometry::{Circle, MultiLine, Point};
wai_bindgen_rust::export!("geometry.wai");
struct Geometry;
impl geometry::Geometry for Geometry {
fn distance_between(p1: Point, p2: Point) -> f32 {
let Point { x: x1, y: y1 } = p1;
let Point { x: x2, y: y2 } = p2;
((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt()
}
fn perimeter_of_circle(c: Circle) -> f32 {
let Circle { center: _, radius } = c;
(2.0 * 22.0 * radius as f32) / 7.0
}
fn area_of_circle(c: Circle) -> f32 {
let Circle { center: _, radius } = c;
(22.0 * (radius * radius) as f32) / 7.0
}
fn multi_line_length(l: MultiLine) -> f32 {
if l.points.len() == 0 {
return 0.0;
}
let mut result = 0.0;
for i in 1..l.points.len() {
let p1 = l.points[i - 1];
let p2 = l.points[i];
result += Geometry::distance_between(p1, p2);
}
result
}
}
Here, we defined multiple functions such as:
perimeter_of_circle
area_of_circle
multi_line_length
All of these functions show how the nested records can be used to perform operations.
Publishing
Similar to last time, if we want to publish our package to WAPM, we'll need to
update our Cargo.toml
file.
# Cargo.toml
[package]
...
description = "Geometrical representations using points"
[lib]
crate-type = ["cdylib", "rlib"]
[package.metadata.wapm]
namespace = "wasmer"
abi = "none"
bindings = { wai-bindgen = "0.1.0", exports = "geometry.wai" }
Now, we can publish it to WAPM.
$ cargo wapm
Successfully published package `wasmer/tutorial-03@0.1.0`