├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── src └── main.rs └── zig ├── build.zig └── src └── zig.zig /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | **/*.dll 13 | **/*.h 14 | **/*.lib 15 | **/*.pdb 16 | **/*.obj 17 | **/*.o 18 | **/*.a 19 | /zig/zig-cache/ -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zigiffy" 3 | version = "0.1.0" 4 | authors = ["DutchGhost "] 5 | edition = "2018" 6 | links = "hello" 7 | build = "build.rs" 8 | 9 | [dependencies] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Dodo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zigiffy 2 | Rust FFI with Zig 3 | 4 | This is a project grown out of curiosity in Rust and Zig. The idea is to make Rust interact with Zig code. 5 | This works, because both languages have the ability to make use of C FFI. 6 | 7 | The name `zigiffy` is a combination of `Zig` and `FFI`. 8 |
9 | The counterpart, `rustiffy` also exists: [rustiffy](https://github.com/DutchGhost/rustiffy) 10 | 11 | # Build 12 | This projects works in Windows under the WSL. 13 | In order for `build.rs` to call the zig compiler, one needs to set `ZIG_COMPILER` as an environment variable to the full path of where your zig compiler is located: 14 | ``` 15 | export ZIG_COMPILER=~/zig-linux-x86_64-0.6.0/zig 16 | ``` 17 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::Path; 3 | 4 | use std::process::Command; 5 | 6 | fn main() { 7 | println!("cargo:rerun-if-changed=build.rs"); 8 | let compiler = env::var("ZIG_COMPILER").expect("Failed to find compiler"); 9 | 10 | let dir = env::var("CARGO_MANIFEST_DIR").unwrap(); 11 | let path = Path::new(&dir); 12 | 13 | env::set_current_dir(path.join("zig")).unwrap(); 14 | 15 | Command::new(compiler) 16 | .args(&["build", "-Drelease-fast"]) 17 | .output() 18 | .expect("Failed to compile Zig lib"); 19 | 20 | env::set_current_dir(path).unwrap(); 21 | 22 | println!( 23 | "cargo:rustc-link-search=native={}", 24 | Path::new(&dir).join("zig/zig-cache/lib").display() 25 | ); 26 | 27 | // // On windows, link against ntdll 28 | // #[cfg(target_os = "windows")] 29 | // { 30 | // println!("cargo:rustc-link-lib={}={}", "dylib", "ntdll"); 31 | // } 32 | } 33 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[link(name = "zig", kind = "static")] 2 | extern "C" { 3 | fn printing(buf: *const u8, len: usize); 4 | 5 | fn itoa_u64(n: u64, buf: *mut u8, len: usize); 6 | } 7 | 8 | fn print>(s: S) { 9 | let s: &str = s.as_ref(); 10 | 11 | unsafe { 12 | printing(s.as_ptr(), s.len()); 13 | } 14 | } 15 | 16 | fn itoa(n: u64, buf: &mut [u8]) { 17 | let len = buf.len(); 18 | let ptr = buf.as_mut_ptr(); 19 | 20 | unsafe { 21 | itoa_u64(n, ptr, len); 22 | } 23 | } 24 | fn main() { 25 | let s = String::from("hello"); 26 | 27 | print(&s); 28 | 29 | let n: u64 = 123_456; 30 | 31 | let mut buff = vec![0; 6]; 32 | 33 | itoa(n, &mut buff); 34 | 35 | println!("{:?}", std::str::from_utf8(&buff)); 36 | } 37 | -------------------------------------------------------------------------------- /zig/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Builder = std.build.Builder; 3 | 4 | pub fn build(b: *Builder) void { 5 | const mode = b.standardReleaseOptions(); 6 | const lib = b.addStaticLibrary("zig", "src/zig.zig"); 7 | 8 | // Need these flags in order to compile 9 | lib.bundle_compiler_rt = true; 10 | lib.force_pic = true; 11 | lib.single_threaded = true; 12 | lib.strip = true; 13 | 14 | lib.setBuildMode(mode); 15 | lib.install(); 16 | 17 | var main_tests = b.addTest("src/zig.zig"); 18 | main_tests.setBuildMode(mode); 19 | 20 | const test_step = b.step("test", "Run library tests"); 21 | test_step.dependOn(&main_tests.step); 22 | } 23 | -------------------------------------------------------------------------------- /zig/src/zig.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const warn = std.debug.warn; 4 | 5 | pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn { 6 | std.os.exit(0xF); 7 | } 8 | 9 | fn pow(base: usize, exp: usize) usize { 10 | var x: usize = base; 11 | var i: usize = 1; 12 | 13 | while (i < exp) : (i += 1) { 14 | x *= base; 15 | } 16 | return x; 17 | } 18 | 19 | export fn add(a: i32, b: i32) callconv(.C) i32 { 20 | return a + b; 21 | } 22 | 23 | export fn printing(buf: [*]const u8, len: usize) callconv(.C) void { 24 | var s = buf[0..len]; 25 | warn("Zig says: {}\n", .{s}); 26 | } 27 | 28 | fn itoa(comptime N: type, n: N, buff: []u8) void { 29 | @setRuntimeSafety(false); 30 | 31 | comptime var UNROLL_MAX: usize = 4; 32 | comptime var DIV_CONST: usize = comptime pow(10, UNROLL_MAX); 33 | 34 | var num = n; 35 | var len = buff.len; 36 | 37 | while (len >= UNROLL_MAX) : (num = std.math.divTrunc(N, num, DIV_CONST) catch return) { 38 | comptime var DIV10: N = 1; 39 | comptime var CURRENT: usize = 0; 40 | 41 | // Write digits backwards into the buffer 42 | inline while (CURRENT != UNROLL_MAX) : ({ 43 | CURRENT += 1; 44 | DIV10 *= 10; 45 | }) { 46 | var q = std.math.divTrunc(N, num, DIV10) catch break; 47 | var r = @truncate(u8, std.math.mod(N, q, 10) catch break) + 48; 48 | buff[len - CURRENT - 1] = r; 49 | } 50 | 51 | len -= UNROLL_MAX; 52 | } 53 | 54 | // On an empty buffer, this will wrapparoo to 0xfffff 55 | len -%= 1; 56 | 57 | // Stops at 0xfffff 58 | while (len != std.math.maxInt(usize)) : (len -%= 1) { 59 | var q: N = std.math.divTrunc(N, num, 10) catch break; 60 | var r: u8 = @truncate(u8, std.math.mod(N, num, 10) catch break) + 48; 61 | buff[len] = r; 62 | num = q; 63 | } 64 | } 65 | 66 | export fn itoa_u64(n: u64, noalias buff: [*]u8, len: usize) callconv(.C) void { 67 | @setRuntimeSafety(false); 68 | var slice = buff[0..len]; 69 | 70 | itoa(u64, n, slice); 71 | } 72 | 73 | test "empty buff" { 74 | var small_buff: []u8 = &[_]u8{}; 75 | 76 | var small: u64 = 100; 77 | 78 | _ = itoa_u64(small, small_buff.ptr, small_buff.len); 79 | } 80 | 81 | test "small buff" { 82 | const assert = @import("std").debug.assert; 83 | const mem = @import("std").mem; 84 | 85 | comptime var small_buff = [_]u8{10} ** 3; 86 | 87 | comptime var small: u64 = 100; 88 | 89 | // Should only run the 2nd while-loop, which is kinda like a fixup loop. 90 | comptime itoa_u64(small, &small_buff, small_buff.len); 91 | 92 | assert(mem.eql(u8, &small_buff, "100")); 93 | } 94 | 95 | test "big buff" { 96 | const assert = @import("std").debug.assert; 97 | const mem = @import("std").mem; 98 | 99 | comptime var big_buff = [_]u8{0} ** 10; 100 | 101 | comptime var big: u64 = 1234123412; 102 | 103 | comptime itoa_u64(big, &big_buff, big_buff.len); 104 | 105 | assert(mem.eql(u8, &big_buff, "1234123412")); 106 | } 107 | 108 | test "unroll count buf" { 109 | const assert = @import("std").debug.assert; 110 | const mem = @import("std").mem; 111 | 112 | comptime var small_buff = [_]u8{10} ** 4; 113 | 114 | comptime var small: u64 = 1000; 115 | 116 | // Should only run the 2nd while-loop, which is kinda like a fixup loop. 117 | comptime itoa_u64(small, &small_buff, small_buff.len); 118 | 119 | assert(mem.eql(u8, &small_buff, "1000")); 120 | } 121 | --------------------------------------------------------------------------------