Pass a string from Go and Rust to C safely
This tests FFI ownership and null-termination. In Go, use C.CString then C.free it. In Rust, create a std::ffi::CString, bind it to a let, then pass as_ptr while the binding lives. Red flag: claiming Rust is auto-safe without mentioning the temp-drop gotcha.
WHAT THIS TESTS: This question probes your understanding of FFI string semantics, specifically the gap between managed language strings and C's null-terminated char pointer convention. The interviewer cares whether you recognize that crossing the language boundary transfers memory ownership responsibilities and that both Go and Rust require explicit steps to produce a valid C string. You must demonstrate knowledge of allocation, null termination, pointer lifetime, and cleanup.
A GOOD ANSWER COVERS: A strong response hits four points in order. First, for Go, explain that you import C and use C.CString to allocate a null-terminated copy on the C heap, pass the resulting pointer to print_message, and then release it with C.free because the Go garbage collector does not manage C memory. Second, for Rust, explain that std::ffi::CString::new creates an owned, null-terminated buffer after verifying there are no interior null bytes, and you obtain a raw pointer with as_ptr. Third, emphasize the Rust lifetime gotcha: you must bind the CString to a named let binding before calling as_ptr, because invoking as_ptr on a temporary CString drops the allocation immediately and leaves a dangling pointer. Fourth, note that both languages assume UTF-8 compatibility here, and if the C function stores the pointer beyond the call duration you need a different ownership strategy.
COMMON WRONG ANSWERS: Watch for three dangerous patterns. One is passing a Go string or byte slice directly to C, which fails because Go strings are not null-terminated and the GC may move the underlying array. Two is forgetting to call C.free in Go, which leaks memory on the C heap. Three is the Rust temporary drop bug, where a candidate writes CString::new("msg").unwrap().as_ptr() inside an unsafe block and claims Rust memory safety prevented any issues, not realizing that raw pointers bypass the borrow checker and the CString was freed before the C call used it.
LIKELY FOLLOW-UPS: An interviewer might push on what happens if print_message stores the pointer for later use, which would require the string to live at least as long as the C library needs it. They may ask how you would handle a string that contains interior null bytes, forcing a discussion of length-delimited buffers or escape strategies. They could also ask about the performance overhead of allocating and copying a new null-terminated buffer on every call, or how to safely receive a string back from C into Rust or Go.
ONE CONCRETE EXAMPLE: In Rust, the incorrect approach is let ptr = CString::new("hello").unwrap().as_ptr(); unsafe { print_message(ptr); } because the CString is dropped at the semicolon and ptr dangles. The correct approach is let c_msg = CString::new("hello").unwrap(); unsafe { print_message(c_msg.as_ptr()); } ensuring c_msg lives through the call. In Go, the incorrect approach is passing a native string directly. The correct approach is msg := C.CString("hello"); defer C.free(unsafe.Pointer(msg)); C.print_message(msg).
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.