[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fSX0uUBBPlbic6beI4wNse7q40_bL8srEllgqcmE0L2A":3},{"item":4,"related":48},{"id":5,"type":6,"title":7,"slug":8,"summary":9,"body":10,"coverUrl":11,"productScreenshots":12,"productLinks":13,"authorName":14,"authorUrl":15,"authorSubject":16,"category":17,"tags":22,"sourceLabel":39,"sourceName":40,"sourceUrl":41,"status":42,"seoTitle":7,"seoDescription":9,"canonicalUrl":39,"isFeatured":43,"sno":44,"sortOrder":45,"publishedAt":46,"updatedAt":47,"createdAt":47},"991de9cb-2702-4dff-9c15-37d014584c56","article","FFI：Python、Rust、C 为什么能互相调用？","foreign-function-interface-ffi-explained","FFI 让不同语言通过共同的调用约定交换函数、数据和内存责任。本文从 Rust 调用 C 的例子讲清结构体布局、字符串、所有权、线程安全与 unsafe 边界，说明跨语言桥梁为什么需要最小合同。","当 Rust 调用 C 库、Python 调用 C 扩展、JavaScript 调用 WebAssembly 时，语言之间需要一座桥。FFI，Foreign Function Interface，中文常译为“外部函数接口”，描述的正是这种跨语言边界：一边如何声明函数，另一边如何传递参数、返回结果和管理内存。\n\n## 跨语言调用真正难在哪里\n\n函数名只是表面。两种语言还必须约定整数宽度、结构体布局、字符串是否以零结尾、数组长度由谁提供、指针是否为空、内存由谁分配和释放，以及异常和线程安全如何处理。\n\nRustonomicon 用 C 接口示例展示了典型做法：通过 `extern` 声明外部函数，使用链接属性寻找库，并在需要时用 `#[repr(C)]` 保证结构体布局与 C 兼容。Rust 编译器无法检查外部库的真实实现，所以外部函数通常需要在 `unsafe` 块中调用。\n\n## 为什么 FFI 边界通常不应该太宽\n\n边界越宽，双方要共享的语义越多。把复杂对象、异常、回调和所有权直接穿过语言边界，出错时很难定位。更稳妥的接口会把边界收敛成少数稳定的 C ABI 函数，明确输入长度和释放函数，并在进入安全代码前做验证。\n\n字符串是常见陷阱。Rust 的字符串不天然以 `\\0` 结尾，C 字符串却经常依赖 NUL terminator；一个看似普通的转换，如果忘了终止符或生命周期，可能产生截断、越界或悬空指针。\n\n## AI 编程的实际提醒\n\n让 AI 生成绑定代码时，不能只让它“把类型翻译过去”。应该同时要求列出 ABI、所有权、线程安全、错误码、释放方式和测试方案。跨语言接口最危险的 bug，往往不是语法错误，而是两种语言都认为责任在对方。\n\n## 读者应该记住\n\nFFI 的本质是把两种语言都能理解的最小合同写清楚。函数可以互相调用，不代表内存、异常和生命周期已经自动安全；边界设计才是跨语言工程的核心。\n\n资料：[The Rustonomicon：Foreign Function Interface](https:\u002F\u002Fdoc.rust-lang.org\u002Fnomicon\u002Fffi.html)；[Rust 中安全与不安全的交界](https:\u002F\u002Fdoc.rust-lang.org\u002Fstable\u002Fnomicon\u002Fsafe-unsafe-meaning.html)","\u002Fuploads\u002F2026-09-20\u002Fc445f48f-6ab6-41ad-bcaf-5fb07665fd0b.jpg",[],[],"Foundit","https:\u002F\u002Ffoundit.cn","foundit-ai-editorial",{"id":18,"name":19,"slug":20,"description":21},"6179d3b6-dc34-4483-9ded-3cd9f1b37a47","科普","abbreviation","介绍各领域新兴概念",[23,27,31,35],{"id":24,"name":25,"slug":26},"144abe77-0dc6-4f66-a176-20bddb1c0bfa","编程","coding",{"id":28,"name":29,"slug":30},"a202d639-99a6-488a-a712-4d4c6ffd7e15","开发","dev",{"id":32,"name":33,"slug":34},"7c76bfc2-f80f-4ee0-a95d-27bd8708b434","技术","slug",{"id":36,"name":37,"slug":38},"4c2bbea6-eab7-40a8-8447-1de478ff7749","分析","analyse",null,"The Rustonomicon：Foreign Function Interface","https:\u002F\u002Fdoc.rust-lang.org\u002Fnomicon\u002Fffi.html","published",false,57,0,"2026-09-20T00:00:00.000Z","2026-09-20T03:58:03.033Z",[49,57,65],{"id":50,"type":6,"title":51,"slug":52,"summary":53,"coverUrl":54,"authorName":14,"sno":55,"publishedAt":46,"createdAt":56},"d0700249-defa-43a1-a66a-4455c6889072","ABI：为什么源码能编译，二进制却不能互相调用？","application-binary-interface-abi-explained","ABI 是二进制世界的调用合同，规定参数传递、对象布局、符号命名和异常处理。本文区分 ABI 与 API，解释动态库、C++ 兼容性和跨语言绑定为什么不能只看函数签名。","\u002Fuploads\u002F2026-09-20\u002F33666a48-4e8f-42a3-9ffd-f4c490498689.jpg",42,"2026-09-20T03:58:01.058Z",{"id":58,"type":6,"title":59,"slug":60,"summary":61,"coverUrl":62,"authorName":14,"sno":63,"publishedAt":46,"createdAt":64},"c9f4e936-9533-4b9c-a1de-f03ef09fed37","WAL：为什么数据库要先写日志，再写真正数据？","write-ahead-logging-wal-database-explained","WAL 要求描述数据变化的日志先于数据页持久化，让数据库可以延迟刷写并在崩溃后通过重放恢复。本文用账本和收据解释 REDO、检查点、复制与持久性设置的关系。","\u002Fuploads\u002F2026-09-20\u002F6260c953-345b-4c9d-b3d6-6202c7e5539c.jpg",43,"2026-09-20T03:58:15.082Z",{"id":66,"type":6,"title":67,"slug":68,"summary":69,"coverUrl":70,"authorName":14,"sno":71,"publishedAt":46,"createdAt":72},"41d50774-50df-4e2f-b1b3-ab6d9f329726","Backpressure：生产者太快时，系统怎样不被数据淹没？","backpressure-reactive-streams-explained","Backpressure 让下游处理能力反过来影响上游生产速度，避免异步流水线靠无限缓存硬撑。本文用水管和阀门解释响应式流、需求信号、数据丢弃与容量设计，也说明它和普通限流的区别。","\u002Fuploads\u002F2026-09-20\u002F35c47792-3d4b-46ae-9375-c68e4c53330e.jpg",45,"2026-09-20T03:58:08.835Z"]