Skip to content

Commit de5d6db

Browse files
authored
Merge pull request #4 from vdods/master
Added demonstration of linking Rust lib statically
2 parents d003bb9 + 9f585b9 commit de5d6db

File tree

10 files changed

+115
-14
lines changed

10 files changed

+115
-14
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
main_shared
2+
main_static

Makefile

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,38 @@
11
ROOT_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
22

3-
build:
3+
# PHONY means that it doesn't correspond to a file; it always runs the build commands.
4+
5+
.PHONY: build-all
6+
build-all: build-shared build-static
7+
8+
.PHONY: run-all
9+
run-all: run-shared run-static
10+
11+
.PHONY: build-shared
12+
build-shared:
413
cd lib/hello && cargo build --release
514
cp lib/hello/target/release/libhello.so lib/
6-
go build -ldflags="-r $(ROOT_DIR)lib" main.go
15+
go build -ldflags="-r $(ROOT_DIR)lib" main_shared.go
16+
17+
.PHONY: build-static
18+
build-static:
19+
cd lib/hello && cargo build --release
20+
cp lib/hello/target/release/libhello.a lib/
21+
go build main_static.go
22+
23+
.PHONY: run-shared
24+
run-shared:
25+
RUST_LOG=trace ./main_shared
26+
27+
.PHONY: run-static
28+
run-static:
29+
RUST_LOG=trace ./main_static
30+
31+
# This is just for running the Rust lib tests natively via cargo.
32+
.PHONY: test-rust-lib
33+
test-rust-lib:
34+
cd lib/hello && RUST_LOG=trace cargo test -- --nocapture
735

8-
run: build
9-
./main
36+
.PHONY: clean
37+
clean:
38+
rm -rf main_shared main_static lib/libhello.so lib/libhello.a lib/hello/target

README.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
# Rust + Golang
22
---
33

4-
This repository shows how, by combining
5-
[`cgo`](https://blog.golang.org/c-go-cgo) and
6-
[Rust's FFI capabilities](https://doc.rust-lang.org/book/ffi.html), we can call
4+
This repository shows how, by combining [`cgo`](https://blog.golang.org/c-go-cgo)
5+
and [Rust's FFI capabilities](https://doc.rust-lang.org/book/ffi.html), we can call
76
Rust code from Golang.
87

9-
Run `make build` and then `./main` to see `Rust` + `Golang` in action. You
10-
should see `Hello John Smith!` printed to `stdout`.
8+
Run `make build-all` and then `make run-all` to see `Rust` + `Golang` in action,
9+
building and running two binaries, one where the Rust lib is compiled to a shared
10+
lib, and one to a static lib. You should see some log output printed to stderr and
11+
then `Hello John Smith!` printed to `stdout`. This results in the binaries
12+
`main_shared` and `main_static` which you can run. See [Makefile](Makefile) in
13+
this repository for more useful make targets.
1114

1215
## You can do this for your own project
1316
Begin by creating a `lib` directory, where you will keep your Rust libraries.
@@ -19,6 +22,8 @@ types that you used.
1922
All that is left to do is to add some `cgo`-specific comments to your Golang
2023
code. These comments tell `cgo` where to find the library and its headers.
2124

25+
For importing a shared lib into your go code, the following must go along with an
26+
additional option given to `go build` (see Makefile):
2227
```go
2328
/*
2429
#cgo LDFLAGS: -L./lib -lhello
@@ -27,7 +32,15 @@ code. These comments tell `cgo` where to find the library and its headers.
2732
import "C"
2833
```
2934

30-
> There should not be a newline between `*/` and `import "C"`.
35+
For a static lib, the additional `-ldl` is necessary compared to the shared
36+
lib, presumably because in the shared lib linking, `dl` is linked in some other way
37+
by the magic of shared libs. To import a static lib into your go code:
38+
```
39+
/*
40+
#cgo LDFLAGS: ./lib/libhello.a -ldl
41+
#include "./lib/hello.h"
42+
*/
43+
import "C"
44+
```
3145

32-
A simple `make build` (use the [Makefile](Makefile) in this repository) will
33-
result in a binary that loads your dynamic library.
46+
> There should not be a newline between `*/` and `import "C"`.

lib/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
libhello.a
2+
libhello.so

lib/hello.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1+
// NOTE: You could use https://michael-f-bryan.github.io/rust-ffi-guide/cbindgen.html to generate
2+
// this header automatically from your Rust code. But for now, we'll just write it by hand.
3+
4+
void init_stuff();
15
void hello(char *name);

lib/hello/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Cargo.lock
2+
target

lib/hello/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ name = "hello"
33
version = "0.1.0"
44

55
[lib]
6-
crate-type = ["cdylib"]
6+
# If you only wanted shared lib, you'd use only "cdylib".
7+
# If you only wanted static lib, you'd use only "staticlib".
8+
# This demo shows both.
9+
crate-type = ["cdylib", "staticlib"]
710

811
[dependencies]
912
libc = "0.2.2"
13+
env_logger = "0.8.4"
14+
log = "0.4.14"

lib/hello/src/lib.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,35 @@
1-
extern crate libc;
21
use std::ffi::CStr;
32

3+
#[no_mangle]
4+
pub extern "C" fn init_stuff() {
5+
env_logger::init();
6+
7+
log::trace!("init_stuff() trace level message");
8+
log::debug!("init_stuff() debug level message");
9+
log::info!("init_stuff() info level message");
10+
log::warn!("init_stuff() warn level message");
11+
log::error!("init_stuff() error level message");
12+
}
13+
414
#[no_mangle]
515
pub extern "C" fn hello(name: *const libc::c_char) {
616
let buf_name = unsafe { CStr::from_ptr(name).to_bytes() };
717
let str_name = String::from_utf8(buf_name.to_vec()).unwrap();
818
println!("Hello {}!", str_name);
919
}
20+
21+
// This is present so it's easy to test that the code works natively in Rust via `cargo test
22+
#[cfg(test)]
23+
pub mod test {
24+
25+
use std::ffi::CString;
26+
use super::*;
27+
28+
// This is meant to do the same stuff as the main function in the .go files.
29+
#[test]
30+
fn simulated_main_function () {
31+
init_stuff();
32+
hello(CString::new("John Smith").unwrap().into_raw());
33+
}
34+
35+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package main
22

3+
// NOTE: There should be NO space between the comments and the `import "C"` line.
4+
35
/*
46
#cgo LDFLAGS: -L./lib -lhello
57
#include "./lib/hello.h"
68
*/
79
import "C"
810

911
func main() {
12+
C.init_stuff()
1013
C.hello(C.CString("John Smith"))
1114
}

main_static.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package main
2+
3+
// NOTE: There should be NO space between the comments and the `import "C"` line.
4+
// The -ldl is necessary to fix the linker errors about `dlsym` that would otherwise appear.
5+
6+
/*
7+
#cgo LDFLAGS: ./lib/libhello.a -ldl
8+
#include "./lib/hello.h"
9+
*/
10+
import "C"
11+
12+
func main() {
13+
C.init_stuff()
14+
C.hello(C.CString("John Smith"))
15+
}

0 commit comments

Comments
 (0)