diff --git a/csharp-book/src/ch14-unsafe-rust-and-ffi.md b/csharp-book/src/ch14-unsafe-rust-and-ffi.md
index 1ea7ae9..3456cfc 100644
--- a/csharp-book/src/ch14-unsafe-rust-and-ffi.md
+++ b/csharp-book/src/ch14-unsafe-rust-and-ffi.md
@@ -10,6 +10,7 @@ Unsafe Rust allows you to perform operations that the borrow checker cannot veri
> **Advanced coverage**: For safe abstraction patterns over unsafe code (arena allocators, lock-free structures, custom vtables), see [Rust Patterns](../../rust-patterns-book/src/summary.md).
### When You Need Unsafe
+
```rust
// 1. Dereferencing raw pointers
let mut value = 42;
@@ -43,6 +44,7 @@ unsafe trait UnsafeTrait {
```
### C# Comparison: unsafe Keyword
+
```csharp
// C# unsafe - similar concept, different scope
unsafe void UnsafeExample()
@@ -67,6 +69,7 @@ unsafe void PinnedExample()
```
### Safe Wrappers
+
```rust
/// The key pattern: wrap unsafe code in a safe API
pub struct SafeBuffer {
@@ -108,7 +111,7 @@ graph LR
end
MI -->|"C ABI call"| FFI["FFI Boundary"]
subgraph "Rust cdylib (.so / .dll)"
- FFI --> RF["extern \"C\" fn
#[no_mangle]"]
+ FFI --> RF["extern #quot;C#quot; fn
#[no_mangle]"]
RF --> Safe["Safe Rust
internals"]
end
@@ -118,6 +121,7 @@ graph LR
```
### Rust Library (compiled as cdylib)
+
```rust
// src/lib.rs
#[no_mangle]
@@ -149,6 +153,7 @@ crate-type = ["cdylib"]
```
### C# Consumer (P/Invoke)
+
```csharp
using System.Runtime.InteropServices;
@@ -176,6 +181,7 @@ When exposing Rust functions to C#, these rules prevent the most common bugs:
2. **`#[no_mangle]`** — prevents the Rust compiler from mangling the function name. Without it, C# can't find the symbol.
3. **Never let a panic cross the FFI boundary** — a Rust panic unwinding into C# is **undefined behavior**. Catch panics at FFI entry points:
+
```rust
#[no_mangle]
pub extern "C" fn safe_ffi_function() -> i32 {
@@ -190,6 +196,7 @@ When exposing Rust functions to C#, these rules prevent the most common bugs:
```
4. **Opaque vs transparent structs** — if C# only holds a pointer (opaque handle), `#[repr(C)]` is not needed. If C# reads struct fields via `StructLayout`, you **must** use `#[repr(C)]`:
+
```rust
// Opaque — C# only holds IntPtr. No #[repr(C)] needed.
pub struct Connection { /* Rust-only fields */ }
@@ -208,6 +215,7 @@ When exposing Rust functions to C#, these rules prevent the most common bugs:
This pattern is common in production: Rust owns an object, C# holds an opaque handle, and explicit create/destroy functions manage the lifecycle.
**Rust side** (`src/lib.rs`):
+
```rust
use std::ffi::{c_char, CStr};
@@ -261,6 +269,7 @@ pub extern "C" fn processor_free(ptr: *mut ImageProcessor) {
```
**C# side**:
+
```csharp
using System.Runtime.InteropServices;
@@ -326,6 +335,7 @@ extern "C" {
```
Requirements:
+
1. Create a `SafeBuffer` struct that wraps the raw pointer
2. Implement `Drop` to call `lib_free_buffer`
3. Provide a safe `&[u8]` view via `as_slice()`
@@ -378,6 +388,3 @@ fn process(buf: &SafeBuffer) {
***
-
-
-