如何使用字符串作为参数从 Go 调用 Rust 函数?

Posted

技术标签:

【中文标题】如何使用字符串作为参数从 Go 调用 Rust 函数?【英文标题】:How do I call a Rust function from Go with a string as a parameter? 【发布时间】:2021-04-23 00:13:23 【问题描述】:

我一直在尝试将字符串传递给 Rust 函数(编译为 Wasm),但是据我了解,目前无法直接传递字符串,因为“str”不是“ FFI 世界”(至少 rust 编译器是这么说的): = help: consider using `*const u8` and a length instead

所以我所做的就是将函数更改为这种形式(而不是使用简单的 &str 类型):

#[no_mangle]
pub extern "C" fn greet(s: *mut u8, len: usize) 
    let s = std::str::from_utf8(unsafe  std::slice::from_raw_parts(s, len) ).unwrap();
    println!("Hello, !", s)

这意味着我需要一个指针和u8中字符串的长度。

但是,有人让我注意到 WASM 模块是沙盒的,因此它们不能像普通应用程序那样使用普通指针。因此,我必须使用这样的函数将内存分配到模块的线性内存中:

use std::alloc::alloc, dealloc, Layout;
#[no_mangle]
pub unsafe fn my_alloc(len: usize) -> *mut u8 
    let align = std::mem::align_of::<usize>();
    let layout = Layout::from_size_align_unchecked(size, align);
    alloc(layout)

这是一个 JS 函数的示例,它使用了这样的 alloc 函数:

function copyMemory(data, instance) 
  var ptr = instance.exports.alloc(data.length);
  var mem = new Uint8Array(instance.exports.memory.buffer, ptr, data.length);
  mem.set(new Uint8Array(data));
  return ptr;

我的问题是我不知道如何将此函数转换为 Go,那是因为我卡在“var mem”行,原因如下:

我在 Go 中找不到“instance.exports.memory.buffer”的等效项(实例是“*wasmtime.Instance”类型)。 我不知道 Unit8Buffer 在 Go 中的作用。

关于 Wasm 内存的好读物:(https://radu-matei.com/blog/practical-guide-to-wasm-memory/#exchanging-strings-between-modules-and-runtimes)

【问题讨论】:

不确定我是否理解你的问题,但我会说指向切片的指针与指向切片第一个元素的指针不同(就像数组一样) @HymnsForDisco,如果我不清楚,我很抱歉。我会尽量让它更容易理解。谢谢你的回答。 您可以使用&amp;s[0] 来获取指向切片的第一个元素的指针(切片是连续的内存,因此只要您传递正确的len,就应该是安全的)。虽然我不是 wasmtime 用户,所以我不知道这是否会实现你的最终目标,但这是我的 2 美分。 老实说,我迷路了,一切都有帮助。谢谢 【参考方案1】:

花了我一点时间来了解 wasmtime Go-package 的工作原理,但最终我解决了我的问题:

func main() 
    dir, err := ioutil.TempDir("", "out")
    check(err)
    defer os.RemoveAll(dir)
    stdoutPath := filepath.Join(dir, "stdout")

    engine := wasmtime.NewEngine()
    store := wasmtime.NewStore(engine)

    linker := wasmtime.NewLinker(store)

    // Configure WASI imports to write stdout into a file.
    wasiConfig := wasmtime.NewWasiConfig()
    wasiConfig.SetStdoutFile(stdoutPath)


    wasi, err := wasmtime.NewWasiInstance(store, wasiConfig, "wasi_snapshot_preview1")
    check(err)

    // Link WASI
    err = linker.DefineWasi(wasi)
    check(err)

    // Create our module
    module, err := wasmtime.NewModuleFromFile(store.Engine, "wasm_file.wasm")
    check(err)
    instance, err := linker.Instantiate(module)
    check(err)


    fn := instance.GetExport("greet").Func()
    memory := instance.GetExport("memory").Memory()
    alloc := instance.GetExport("my_alloc").Func()

    // // string for alloc
    size2 := int32(len([]byte("Elvis")))

    // //Allocate memomory
    ptr2, err := alloc.Call(size2)
    pointer, _ := ptr2.(int32)

    buf := memory.UnsafeData()
    for i, v := range []byte("Elvis") 
        buf[pointer+int32(i)] = v
    

    // Use string func
    _, err = fn.Call(pointer, size2 )
    check(err)
    
    // Print WASM stdout
    out, err := ioutil.ReadFile(stdoutPath)
    check(err)
    fmt.Print(string(out))


【讨论】:

以上是关于如何使用字符串作为参数从 Go 调用 Rust 函数?的主要内容,如果未能解决你的问题,请参考以下文章

使用数组参数从 C 调用 Rust 方法

算法leetcode|1967. 作为子字符串出现在单词中的字符串数目(rust和go重拳出击)

算法leetcode|1967. 作为子字符串出现在单词中的字符串数目(rust和go重拳出击)

Go语言学习-函数

C# 调用具有不同类型的相同扩展函​​数作为参数(可能是委托?)

如何从主函数外部提前退出 Rust 程序?