For the last year I have been collecting various keyboards , which includes writing firmware for various control schemes.
Initially, I wrote them in Rust, but despite years of development experience with it, I had to fight. Over time, I got my keyboards working, but it took an obscene amount of time and did not give me pleasure.
After repeated suggestions from my more Rust-and-compute-savvy friend Jamie Brandon , I rewrote the firmware to Zig and it worked out very well.
I found this astonishing, given that I had never seen Zig before, and this is a language, not even version 1.0 yet, created by a hipster at the University of Portland , and is described, in fact, with just one page of documentation .
The experience went so well that I now understand Zig (which has used a dozen hours) as well as Rust (which I have used at least a thousand hours).
This, of course, reflects not only me and my interests, but also applies to each of these languages. Therefore, I will have to explain what I want from a systems programming language in the first place.
Also, to explain why I was struggling with Rust, I have to show a lot of complex code that I definitely don't like. My goal here is not to reproach Rust, but to show my (insufficient) reputation: this is so that you can judge for yourself whether I am using Rust in a rational way, or if I completely lost my way.
, , " X , Y", , , Rust Zig, , "Zig's !". ( , , Zig, " , , Rust, , ?").
, . PostScript Ruby (, ), JavaScript, . Clojure ( ClojureScript ), .
2017 . - , , , , , -, . , , :
, ; , , .
, , web assembly, ( ) , ..
( ) , ( , , ..).
.
, Rust 1.18.
Rust, , , : WASM , (Rust Electron), Rust- stm32g4, - ( ; !).
, Rust. - , , Rust , , /. : , .
, , Rust .
, Rust, , , 4- dev-kit' / Atreus'a:
" ". ( , , , 10-100 ). Rust "features", Cargo.toml
:
[dependencies]
cortex-m = "0.6"
nrf52840-hal = { version = "0.11", optional = true, default-features = false }
nrf52833-hal = { version = "0.11", optional = true, default-features = false }
arraydeque = { version = "0.4", default-features = false }
heapless = "0.5"
[features]
keytron = ["nrf52833"]
keytron-dk = ["nrf52833"]
splitapple = ["nrf52840"]
splitapple-left = ["splitapple"]
splitapple-right = ["splitapple"]
# specify a default here so that rust-analyzer can build the project; when building use --no-default-features to turn this off
default = ["keytron"]
nrf52840 = ["nrf52840-hal"]
nrf52833 = ["nrf52833-hal"]
, keytron
. nrf52833
( ), nrf52833-hal
( , , Rust). Rust . , , :
#[cfg(feature = "nrf52833")]
pub use nrf52833_hal::pac as hw;
#[cfg(feature = "nrf52840")]
pub use nrf52840_hal::pac as hw;
:
fn read_keys() -> Packet {
let device = unsafe { hw::Peripherals::steal() };
#[cfg(any(feature = "keytron", feature = "keytron-dk"))]
let u = {
let p0 = device.P0.in_.read().bits();
let p1 = device.P1.in_.read().bits();
//invert because keys are active low
gpio::P0::pack(!p0) | gpio::P1::pack(!p1)
};
#[cfg(feature = "splitapple")]
let u = gpio::splitapple::read_keys();
Packet(u)
}
, :
- (
any
#[cfg(any(feature = "keytron", feature = "keytron-dk"))]
).
optional = true
,Cargo.toml
( !).
(
cargo build --release --no-default-features --features "keytron"
)
!
- , , - "" :
fn read_keys(port: #[cfg(feature = "splitapple")]
nrf52840_hal::pac::P1
#[cfg(feature = "keytron")]
nrf52833_hal::pac::P0) -> Packet {}
, RTIC, app
, , , :
#[app(device = nrf52833)]
const APP: () = {
//your code here...
};
? .
Rust .
: , ( ) :
, . , 1.10 (col0) 0.13 ( 1) , , K8 . , Rust :
.
Rust.
, .
, , P0's pin 10, :
P0.pin_cnf[10].write(|w| {
w.input().disconnect();
w.dir().output();
w
});
, :
for (port, pin) in &[(P0, 10), (P1, 7), ...] {
port.pin_cnf[pin].write(|w| {
w.input().disconnect();
w.dir().output();
w
});
}
, - (P0, usize) (P1, usize) - .
, :
type PinIdx = u8;
type Port = u8;
const COL_PINS: [(Port, PinIdx); 7] =
[(1, 10), (1, 13), (1, 15), (0, 2), (0, 29), (1, 0), (0, 17)];
pub fn init_gpio() {
for (port, pin_idx) in &COL_PINS {
match port {
0 => {
device.P0.pin_cnf[*pin_idx as usize].write(|w| {
w.input().disconnect();
w.dir().output();
w
});
}
1 => {
device.P1.pin_cnf[*pin_idx as usize].write(|w| {
w.input().disconnect();
w.dir().output();
w
});
}
_ => {}
}
}
}
, .
, , , ? , , :
pub fn read_keys() -> u64 {
let device = unsafe { crate::hw::Peripherals::steal() };
let mut keys: u64 = 0;
macro_rules! scan_col {
($col_idx: tt; $($row_idx: tt => $key:tt, )* ) => {
let (port, pin_idx) = COL_PINS[$col_idx];
////////////////
//set col high
unsafe {
match port {
0 => {
device.P0.outset.write(|w| w.bits(1 << pin_idx));
}
1 => {
device.P1.outset.write(|w| w.bits(1 << pin_idx));
}
_ => {}
}
}
cortex_m::asm::delay(1000);
//read rows and move into packed keys u64.
//keys are 1-indexed.
let val = device.P0.in_.read().bits();
$(keys |= ((((val >> ROW_PINS[$row_idx]) & 1) as u64) << ($key - 1));)*
////////////////
//set col low
unsafe {
match port {
0 => {
device.P0.outclr.write(|w| w.bits(1 << pin_idx));
}
1 => {
device.P1.outclr.write(|w| w.bits(1 << pin_idx));
}
_ => {}
}
}
};
};
//col_idx; row_idx => key ID
#[cfg(feature = "splitapple-left")]
{
scan_col!(0; 0 => 1 , 1 => 8 , 2 => 15 , 3 => 21 , 4 => 27 , 5 => 33 ,);
scan_col!(1; 0 => 2 , 1 => 9 , 2 => 16 , 3 => 22 , 4 => 28 , 5 => 34 ,);
scan_col!(2; 0 => 3 , 1 => 10 , 2 => 17 , 3 => 23 , 4 => 29 , 5 => 35 ,);
scan_col!(3; 0 => 4 , 1 => 11 , 2 => 18 , 3 => 24 , 4 => 30 , 5 => 36 ,);
scan_col!(4; 0 => 5 , 1 => 12 , 2 => 19 , 3 => 25 , 4 => 31 , 5 => 37 ,);
scan_col!(5; 0 => 6 , 1 => 13 , 2 => 20 , 3 => 26 , 4 => 32 , 5 => 38 ,);
scan_col!(6; 0 => 7 , 1 => 14 ,);
}
#[cfg(feature = "splitapple-right")]
{
scan_col!(0; 0 => 1 , 1 => 8 , 2 => 15 , 3 => 23 , 4 => 30 , 5 => 37 ,);
scan_col!(1; 0 => 2 , 1 => 9 , 2 => 16 , 3 => 24 , 4 => 31 , 5 => 38 ,);
scan_col!(2; 0 => 3 , 1 => 10 , 2 => 17 , 3 => 25 , 4 => 32 , 5 => 39 ,);
scan_col!(3; 0 => 4 , 1 => 11 , 2 => 18 , 3 => 26 , 4 => 33 , 5 => 40 ,);
scan_col!(4; 0 => 5 , 1 => 12 , 2 => 19 , 3 => 27 , 4 => 34 , 5 => 41 ,);
scan_col!(5; 0 => 6 , 1 => 13 , 2 => 20 , 3 => 28 , 4 => 35 , 5 => 42 ,);
scan_col!(6; 0 => 7 , 1 => 14 , 2 => 21 , 3 => 29 , 4 => 36 , 5 => 22 ,);
}
keys
}
!
, scan_col!
, , keys
: u64 .
, , , , . Google " Rust" , :
, , , , Rust , , . C (#define, #ifdef
..), , Rust . ( !). Rust - Rust Analyzer , , , C.
, Rust - , RFC - , , , .
, , ?
, Zig , - , -- - .
Zig,
: , - Zig, .
.
, dk.zig
usingnamespace @import("register-generation/target/nrf52833.zig");
usingnamespace @import("ztron.zig");
pub const led = .{ .port = p0, .pin = 13 };
atreus.zig
usingnamespace @import("register-generation/target/nrf52840.zig");
usingnamespace @import("ztron.zig");
pub const led = .{ .port = p0, .pin = 11 };
.
ztron.zig
@import("root")
("root" - , ; !) :
usingnamespace @import("root");
export fn setup() void {
led.port.pin_cnf[led.pin].modify(.{
.dir = .output,
.input = .disconnect,
});
}
"feature" , Cargo.toml
, . Cargo.toml !
, , : devkit, zig build-obj dk.zig
; Atreus - zig build-obj atreus.zig
.
, Zig , . ( - , , ).
- ? , , ... :
const rows = .{
.{ .port = p1, .pin = 0 },
.{ .port = p1, .pin = 1 },
.{ .port = p1, .pin = 2 },
.{ .port = p1, .pin = 4 },
};
const cols = .{
.{ .port = p0, .pin = 13 },
.{ .port = p1, .pin = 15 },
.{ .port = p0, .pin = 17 },
.{ .port = p0, .pin = 20 },
.{ .port = p0, .pin = 22 },
.{ .port = p0, .pin = 24 },
.{ .port = p0, .pin = 9 },
.{ .port = p0, .pin = 10 },
.{ .port = p0, .pin = 4 },
.{ .port = p0, .pin = 26 },
.{ .port = p0, .pin = 2 },
};
pub fn initKeyboardGPIO() void {
inline for (rows) |x| {
x.port.pin_cnf[x.pin].modify(.{
.dir = .input,
.input = .connect,
.pull = .pulldown,
});
}
inline for (cols) |x| {
x.port.pin_cnf[x.pin].modify(.{
.dir = .output,
.input = .disconnect,
});
}
}
, - , , "" - .
:
const col2row2key = .{
.{ .{ 0, 1 }, .{ 1, 11 }, .{ 2, 21 }, .{ 3, 32 } },
.{ .{ 0, 2 }, .{ 1, 12 }, .{ 2, 22 }, .{ 3, 33 } },
.{ .{ 0, 3 }, .{ 1, 13 }, .{ 2, 23 }, .{ 3, 34 } },
.{ .{ 0, 4 }, .{ 1, 14 }, .{ 2, 24 }, .{ 3, 35 } },
.{ .{ 0, 5 }, .{ 1, 15 }, .{ 2, 25 }, .{ 3, 36 } },
.{ .{ 2, 26 }, .{ 3, 37 } },
.{ .{ 0, 6 }, .{ 1, 16 }, .{ 2, 27 }, .{ 3, 38 } },
.{ .{ 0, 7 }, .{ 1, 17 }, .{ 2, 28 }, .{ 3, 39 } },
.{ .{ 0, 8 }, .{ 1, 18 }, .{ 2, 29 }, .{ 3, 40 } },
.{ .{ 0, 9 }, .{ 1, 19 }, .{ 2, 30 }, .{ 3, 41 } },
.{ .{ 0, 10 }, .{ 1, 20 }, .{ 2, 31 }, .{ 3, 42 } },
};
pub fn readKeys() PackedKeys {
var pk = PackedKeys.new();
inline for (col2row2key) |row2key, col| {
// set col high
cols[col].port.outset.write_raw(1 << cols[col].pin);
delay(1000);
const val = rows[0].port.in.read_raw();
inline for (row2key) |row_idx_and_key| {
const row_pin = rows[row_idx_and_key[0]].pin;
pk.keys[(row_idx_and_key[1] - 1)] = (1 == ((val >> row_pin) & 1));
}
// set col low
cols[col].port.outclr.write_raw(1 << cols[col].pin);
}
return pk;
}
, Ziginline for
, Rust ( , , ), / .
, // const-, . , ( ) :
pub const switch_count = comptime {
var n = 0;
for (col2row2key) |x| n += x.len;
return n;
};
, Rust:
scan_col!(0; 0 => 1 , 1 => 8 , 2 => 15 , 3 => 21 , 4 => 27 , 5 => 33 ,);
( , - , Rust 500 , , ).
Rust'?
Zig Rust, . , , - " " - Rust.
, , Rust - , . , Rust , , , , , .
, , :
fn main() {
let message = "hello world"; // a regular immutable variable definition
}
let message = "hello world"; // doesn't work at toplevel
const message: &str = "hello world"; // you have to write `const` and declare the type yourself.
, , . , :
, , , .
, , " " , .
,
const
,let
, ,let
, consts data- .
- 100 , , , , ..
: , , , , . ( , : , , , , , , , , ..).
, , Rust?
: , ?
Rust , , .
, if
/, , ( ). , , .
, "" , Zig - . , , : comptime
inline for
, , , , , , , - Zig!
Zig?
- , , , , - . , ; =D
, Zig .
- , , : , .
"" : Rust, Emacs , MacBook Air 2013 :
Rust 1.50 70 (, 90 ), target/
450 .
Zig 0.7.1, , 5 , zig-cache/
1.4. !
"" - ; , , . Zig:
, .
, , , - - "".
: ", , while
", .
, , Zig, . //.
Zig, Zig.
, , .
, !
-Zig- WASM, ! (zig build-lib -target wasm32-freestanding -O ReleaseSmall foo.zig
foo.wasm
, !).
, , , Zig . , Zig - , ; , , . .
, . , , Rust, . ; XML Zig- ( continue comptime).
, Zig ; , , , , . . , .
, , , Zig: , .
I either successfully use Zig for my embedded hobby projects, one-time WASM helpers and the necessary bindings to the C API, or, in the struggle to complete these tasks, I finally begin to understand and appreciate more what problems Rust protects me from.
Anyway, I'm very happy!
Acknowledgments
Thanks to Julia Evans , Pierre Yves Bacc, Laura Lindsey , Jamie Brandon, and Boats for their thoughtful discussion of Rust / Zig and constructive feedback on this article!