tezvyn:

Design a safe Rust wrapper taking &[i32] and returning Vec<i32>

AI-drafted, machine-checkedSource: doc.rust-lang.orgadvanced

Tests Rust FFI buffer-output encapsulation. A strong answer declares an unsafe extern C block, allocates a Vec with capacity, passes as_mut_ptr and a local size_t, validates returned length, then calls set_len.

WHAT THIS TESTS: Mastery of Rust's unsafe FFI boundary, specifically the idiomatic pattern for wrapping C functions that write into caller-allocated buffers and report the written count via an out-parameter. The interviewer cares whether you understand the distinction between a Vec's capacity and length, how to pass uninitialized but allocated memory across the FFI boundary without violating Rust's safety guarantees, and how to encapsulate all unsafety so the public API is safe to call.

A GOOD ANSWER COVERS: First, declare the foreign function in an unsafe extern C block using libc::c_int and libc::size_t rather than assuming i32 and usize map directly. Second, explain buffer sizing: since the C function updates output_len with the count written, you must allocate a Vec with enough capacity to hold the maximum possible output, then create a local mut size_t initialized to that capacity to serve as both the available space hint and the actual written count receiver. Third, perform the call inside a tight unsafe block, passing input.as_ptr cast to *const c_int, input.len cast to size_t, output.as_mut_ptr cast to *mut c_int, and a pointer to your local length variable. Fourth, after the call returns, validate that the output_len written by C does not exceed the Vec's allocated capacity; this defends against C bugs that would otherwise cause immediate UB when you adjust the Vec's length. Fifth, call set_len on the Vec with the validated output_len to transition the written elements from uninitialized to initialized, then return the Vec.

COMMON WRONG ANSWERS: Calling set_len before the FFI call, which exposes uninitialized memory to safe Rust and is undefined behavior. Passing a pointer to the Vec's internal length field directly to C instead of using a local size_t variable. Forgetting to validate the output_len returned by C and blindly calling set_len with it. Assuming i32 and c_int or usize and size_t are interchangeable without explicit casts or platform checks. Allocating a Vec and then passing a reference to it directly to C, which does not give C a contiguous pointer and also creates aliasing issues.

LIKELY FOLLOW-UPS: How would you change the design if the output size were unbounded or potentially larger than the input? What if the C function could fail and needed to return an error code? Would you use Pin to prevent the Vec from moving during the call, and why is it usually unnecessary for a synchronous FFI call? How do you handle the case where the C library expects the output_len pointer to be pre-filled with the buffer capacity versus one that only writes the result?

ONE CONCRETE EXAMPLE: The implementation looks like this. In the unsafe extern C block, declare fn process_data(input: *const libc::c_int, input_len: libc::size_t, output: *mut libc::c_int, output_len: *mut libc::size_t). The safe wrapper takes input: &[i32] and returns Vec<i32>. It asserts that libc::c_int and i32 have the same size, then allocates let mut output = Vec::with_capacity(input.len()) and let mut written = output.capacity() as libc::size_t. Inside an unsafe block, call process_data(input.as_ptr() as *const libc::c_int, input.len() as libc::size_t, output.as_mut_ptr() as *mut libc::c_int, &mut written). After the call, assert that written as usize <= output.capacity(), then output.set_len(written as usize), and return output.

Read the original → doc.rust-lang.org

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.