Skip to content

Instantly share code, notes, and snippets.

@umuro
Created November 9, 2023 20:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save umuro/274e6af05c4064b644bd8fe46c375925 to your computer and use it in GitHub Desktop.
Save umuro/274e6af05c4064b644bd8fe46c375925 to your computer and use it in GitHub Desktop.
Tcl with Rust

Implementing Rust functions and calling them from Tcl scripts involves a multi-step process. Here's a high-level overview of how you can achieve this:

  1. Writing Rust Code: Start by writing the desired functions in Rust. You'll need to compile this Rust code into a shared library (.so on Unix-like systems, .dll on Windows).

  2. Creating a C Interface: Rust functions must be exposed with a C-compatible interface (using extern "C"), because most other languages including Tcl can easily interface with C code.

  3. Building a Shared Library: Compile your Rust project with cargo to create a shared library. Make sure to use the appropriate target for your operating system.

  4. Interfacing with Tcl: Tcl can interface with C code using the Foreign Function Interface (FFI) mechanism. You'll use the load command in Tcl to load the shared library.

  5. Calling Rust Functions from Tcl: Once the shared library is loaded in Tcl, you can call the Rust functions as if they were Tcl commands.

Here is a step-by-step guide:

Step 1: Writing Rust Code

// lib.rs
#[no_mangle]
pub extern "C" fn rust_function(input: i32) -> i32 {
    // Your Rust code here
    input * 2
}

Step 2: Creating a C Interface

Ensure your functions use #[no_mangle] to prevent the Rust compiler from mangling the names.

Step 3: Building a Shared Library

You can build a shared library by adding the following to your Cargo.toml:

[lib]
crate-type = ["cdylib"]

Then run cargo build --release.

Step 4: Interfacing with Tcl

In your Tcl script, use the load command to load the shared library you've compiled.

load /path/to/your/library.so

Step 5: Calling Rust Functions from Tcl

After loading the library, you can call the Rust functions using the Tcl FFI mechanism.

# Assuming 'rust_function' is the name of the function
set result [rust_function 10]
puts "The result is $result"

Additional Considerations:

  • Error Handling: Rust's error handling paradigm doesn't map directly onto Tcl's, so you'll need to design your interface accordingly, possibly returning error codes.

  • Data Types: Be mindful of the data types you're passing between Tcl and Rust. You may need to perform conversions or marshalling/unmarshalling of complex data types.

  • Memory Management: Rust manages its own memory, but when interfacing with Tcl, you must ensure there are no memory leaks or undefined behavior due to improper handling of memory across the FFI boundary.

  • Testing: Thoroughly test your Rust functions within the context of a Tcl script to catch any integration issues.

You might need to dive into the documentation for both Rust and Tcl, especially regarding their FFI capabilities, to implement this correctly. This high-level overview gives you a roadmap, but the specifics can get quite technical.

When passing data across the FFI boundary from Rust to Tcl, it's typical for the caller (in this case, Tcl) to be responsible for freeing the resources. However, this can be inconvenient and error-prone. There are a few strategies that can help avoid the need for explicit deallocation from Tcl:

1. Use data types that don't require deallocation

If possible, use simple data types that don't require explicit deallocation, such as integers or booleans. For example, if you can encode your data as an integer handle or ID, you can manage the resources internally in Rust and expose only this handle to Tcl.

2. Automatic Memory Management on the Rust Side

Implement a Rust-side mechanism that keeps track of allocated objects and deallocates them when they're no longer needed.

For example, you could use a reference-counting pointer such as Rc or Arc in Rust. These types will automatically deallocate the memory when the reference count drops to zero, though they're not inherently safe to share across the FFI boundary without extra care.

3. Use Rust's Ownership Model to Ensure Deallocation

This can be done by designing your API such that Rust functions that allocate memory also take responsibility for deallocating it. For instance, you can have Rust functions that return owned data to Tcl in a form that doesn't require deallocation, like returning a string copy that Tcl will inherently copy and manage itself:

#[no_mangle]
pub extern "C" fn rust_return_string() -> *mut c_char {
    let rust_string = "Hello from Rust!".to_owned();
    // Convert the Rust String into a CString
    let c_string = CString::new(rust_string).unwrap();
    // Convert CString into a pointer so it can be returned across FFI
    c_string.into_raw()
}

In Tcl, you would convert the C string to a Tcl string, which is then managed by Tcl's own garbage collector:

load /path/to/your/library.so

# Call the Rust function
set c_string_ptr [rust_return_string]

# Convert C string to Tcl string
set rust_string [FFI::ptr2string $c_string_ptr]

# Free the C string since Tcl has made its own copy of the string now
FFI::free $c_string_ptr

puts "Received string from Rust: $rust_string"

In this example, Tcl's FFI interface would need to provide a ptr2string function that copies the C string into Tcl's own memory management system, after which the C string can be freed.

4. Use Callbacks for Deallocation

Another approach is to use Rust-side callbacks that Tcl can call to perform cleanup. This can be a function pointer that Tcl can call when it's done with the data. This is still a form of explicit deallocation, but it can be hidden behind higher-level abstractions on the Tcl side.

Conclusion

While it's difficult to completely avoid explicit deallocation when crossing the FFI boundary, you can often design your API to minimize the need for it or hide it behind abstractions. The key is to carefully design the ownership and lifecycle of the data being shared between Rust and Tcl.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment