diff --git a/doc/How-to-support-a-Rust-Library.md b/doc/How-to-support-a-Rust-Library.md deleted file mode 100644 index 7b7ba70d..00000000 --- a/doc/How-to-support-a-Rust-Library.md +++ /dev/null @@ -1,335 +0,0 @@ -How to support a Rust Library -===== - -## Add Dependencies & Build Configuration - -Edit `Cargo.toml` to include necessary dependencies and configuration: - -```toml -[dependencies] -libc = "0.2" -csv = "1.1" - -[lib] -crate-type = ["cdylib"] # The generated dynamic library will conform to the C standard - -[build-dependencies] -cbindgen = "0.26.0" -``` - -## C-style wrapper for Rust - -### Import C Language Types - -Use types from the libc package for interoperability with C: - -```rust -use libc::{c_int, c_char, strlen}; -``` - -### Function Decoration and Attributes - -To ensure that Rust functions can be correctly called by C and LLGO, use the following decorators: - -- `#[no_mangle]` prevents the compiler from mangling the function name. -- `unsafe` is used to mark operations that are unsafe, especially when dealing with raw pointers. -- `extern "C"` specifies the use of C calling conventions. - -```rust -pub fn add_numbers(a: i32, b: i32) -> i32 { - a + b -} -``` - -After packaging: - -```rust -#[no_mangle] -pub unsafe extern "C" fn add_numbers_c(a: i32, b: i32) -> i32 { - add_numbers(a, b) -} -``` - -### Memory Management - -Use `Box` to manage dynamic memory to ensure correct memory release between Rust and C: - -```rust -let config = Config::new(); -``` - -After packaging: - -```rust -pub unsafe extern "C" fn sled_create_config() -> \*mut Config { - Box::into_raw(Box::new(Config::new())) -} - -#[no_mangle] -pub unsafe extern "C" fn sled_free_config(config: \*mut Config) { - drop(Box::from_raw(config)); -} -``` - -### Handling Generic Pointers - -Address the interfacing issues between generic pointers in C and Rust: - -```rust -let mut reader = ReaderBuilder::new().from_path(file_path)?; -``` - -After packaging: - -```rust -// Create a new CSV reader for the specified file path. -#[no_mangle] -pub extern "C" fn csv_reader_new(file_path: *const c_char) -> *mut c_void { - let file_path = unsafe { - if file_path.is_null() { return ptr::null_mut(); } - match CStr::from_ptr(file_path).to_str() { - Ok(s) => s, - Err(_) => return ptr::null_mut(), - } - }; - - let reader = csv::ReaderBuilder::new().from_path(file_path); - match reader { - Ok(r) => Box::into_raw(Box::new(r)) as *mut c_void, - Err(_) => ptr::null_mut(), - } -} - -// Free the memory allocated for the CSV reader. -#[no_mangle] -pub extern "C" fn csv_reader_free(ptr: *mut c_void) { - if !ptr.is_null() { - let reader: Box> = unsafe { Box::from_raw(ptr as *mut csv::Reader) }; - std::mem::drop(reader); - } -} -``` - -### String Handling - -Convert strings between C and Rust: - -```rust -let mut record = csv::StringRecord::new(); - -while reader.read_record(&mut record)? { - // Print each record - println!("{:?}", record); -} -``` - -After packaging: - -```rust -// Read the next record from the CSV reader and return it as a C string. -#[no_mangle] -pub extern "C" fn csv_reader_read_record(ptr: *mut c_void) -> *const c_char { - let reader = unsafe { - assert!(!ptr.is_null()); - &mut *(ptr as *mut csv::Reader) - }; - - let mut record = csv::StringRecord::new(); - match reader.read_record(&mut record) { - Ok(true) => match CString::new(format!("{:?}\n", record)) { - Ok(c_string) => c_string.into_raw(), - Err(_) => ptr::null(), - }, - _ => ptr::null(), - } -} - -// Free the memory allocated for a C string returned by other functions. -#[no_mangle] -pub extern "C" fn free_string(s: *mut c_char) { - if s.is_null() { - return; - } - unsafe { - let c_string = CString::from_raw(s); - std::mem::drop(c_string); - } -} -``` - -## Generate Header File - -Edit `cbindgen.toml` to configure the header file generation rules: - -```toml -# See https://github.com/mozilla/cbindgen/blob/master/docs.md#cbindgentoml for -# a list of possible configuration values. -language = "C" -``` - -Use cbindgen to generate a C header file, automating this process through a `build.rs` script: - -```rust -fn main() { - let config = cbindgen::Config::from_file("cbindgen.toml").expect("Config file not found."); - cbindgen::generate_with_config(&crate_dir, config).unwrap().write_to_file("target/include/csv_wrapper.h"); -} -``` - -## Compilation and Installation - -### Build the dynamic library: - -```sh -cargo build --release -``` - -### Install dylib-installer - -Install the [dylib-installer](https://github.com/hackerchai/dylib-installer) tool, which is used to install dynamic libraries: - -```sh -brew tap hackerchai/tap -brew install dylib-installer -``` - -Or you can install it using Cargo: - -```sh -cargo install dylib_installer -``` - -### Install Dynamic Library - -Use dylib-installer to install the built dynamic library and the header file into the system directory: - -```sh -sudo dylib_installer -``` - -### Check the Installation - -You can check the installation by running the following command: - -```sh -pkg-config --libs --cflags -``` - -If everything is installed correctly, you will see the output like this (depending on your system): - -```sh --I/usr/local/include -L/usr/local/lib -l -``` - -## LLGO Mapping - -Map functions from the Rust library to an LLGO package, ensuring type consistency: - -- LLGoPackage - -Specify `LLGoPackage` and use `pkg-config` to find the location of the lib library. - -```go -const ( - LLGoPackage = "link: $(pkg-config --libs csv_wrapper); -lcsv_wrapper" -) -``` - -- Type - -If you want to use variables inside the struct, you can add them accordingly. -If it can't be represented directly or is not needed, it can be represented in the form `Unused []byte`, the length of the array is determined by its size, and if the struct is only used as a pointer, then the array length can be `0`. - -```go -type Reader struct { - Unused [0]byte -} - -// type Reader struct { -// Unused [8]byte -// } -``` - -If we want to calculate the size of this structure, we can use the following C code: - -```c -printf("%d\n", sizeof(csv_reader)); -``` - -- Ordinary functions - -Ordinary functions can be mapped in the form of `//go:linkname`. - -```c -csv_reader *csv_reader_new(const char *file_path); -``` - -After mapping: - -```go -//go:linkname NewReader C.csv_reader_new -func NewReader(file_path *c.Char) *Reader -``` - -- Method - -Methods need to be mapped in the form of `// llgo:link (*Receiver)`. - -```c -void csv_reader_free(csv_reader *reader); - -const char *csv_reader_read_record(csv_reader *reader); -``` - -After mapping: - -We can extract the first parameter as Receiver: - -```go -// llgo:link (*Reader).Free C.csv_reader_free -func (reader *Reader) Free() {} - -// llgo:link (*Reader).ReadRecord C.csv_reader_read_record -func (reader *Reader) ReadRecord() *c.Char { return nil } -``` - -- Function pointer - -If you use a function pointer, that is, declare the function as a type separately, you need to use `// llgo:type C` to declare it. - -```c -typedef size_t (*hyper_io_read_callback)(void*, struct hyper_context*, uint8_t*, size_t); - -void hyper_io_set_read(struct hyper_io *io, hyper_io_read_callback func); -``` - -After mapping: - -```go -// llgo:type C -type IoReadCallback func(c.Pointer, *Context, *uint8, uintptr) uintptr - -// llgo:link (*Io).SetRead C.hyper_io_set_read -func (io *Io) SetRead(callback IoReadCallback) {} -``` - -Or declare the function directly in the parameter. - -```go -// llgo:link (*Io).SetRead C.hyper_io_set_read -func (io *Io) SetRead(ioSetReadCb func(c.Pointer, *Context, *uint8, uintptr) uintptr) {} -``` - -## Writing Examples and README - -Finally, provide example code and a detailed README file to help users understand how to use the generated library. - -## Example Code - -You can find the migrated examples in the [llgoexamples](https://github.com/goplus/llgoexamples). The migrated Rust libraries are in the `lib` directory, and the migrated mapping files and Go demos are in the `rust` directory. - -Such as: - -- CSV: [csv.rs](https://github.com/goplus/llgoexamples/blob/main/lib/rust/csv-wrapper/src/lib.rs) --> [csv.go](https://github.com/goplus/llgoexamples/blob/main/rust/csv/csv.go) -- Sled: [sled.rs](https://github.com/goplus/llgoexamples/blob/main/lib/rust/sled/src/lib.rs) --> [sled.go](https://github.com/goplus/llgoexamples/blob/main/rust/sled/sled.go) -- ...