Skip to content

Returned by value: reading

Beside Returned by value: the caller needs a size · companions: every error · lints

Level: reference · the reading list

One line: Where the rest of the Rust world explains returning by value: what four books say about Sized, Clone and calling conventions, the Reference's one sentence on the unstable Rust ABI, the System V rule and LLVM's sret underneath it, the RFC that let arguments go unsized but kept returns sized, and seven places to read with care, each run on rustc 1.98.0.

Every entry was checked on 2026-09-17: each URL answered 200 to curl, except Stack Overflow, which refuses scripted requests and was checked through the Stack Exchange API, and each #anchor exists on its page. Book sections are copied from the publisher's table of contents (O'Reilly's for Programming Rust, Rust in Action and Effective Rust, No Starch's "Contents in Detail" for Rust for Rustaceans), and each passage described here was read in a copy of the book. Video timestamps are the chapter marks in the video's own description. The video was not watched. "Read with care" names a sentence that fails when run on rustc 1.98.0, x86_64-apple-darwin, and says what ran.

If you read only four

  • Programming Rust, 2nd ed. (Blandy, Orendorff & Tindall, O'Reilly 2021), ch. 13 "Utility Traits" → "Sized" (p. 305) and "Clone" (p. 308). The first says which types are unsized and that you can only handle them through pointers. The second gives the lesson's argument in one sentence: clone returns Self, a function cannot return an unsized value, so Clone extends Sized.
  • Rust for Rustaceans (Jon Gjengset, No Starch 2021), ch. 2 "Types" → "Types in Memory" → "Dynamically Sized Types and Wide Pointers" (p. 23). Its list of what must be Sized names return values, alongside struct fields, function arguments, variable types and array types. The Book's and the Reference's lists leave return values out.
  • The Reference — External blocks → ABI ↗. A calling convention decides, among other things, how return values are passed, and extern "Rust" ↗, the one every plain fn gets, offers no stability guarantees. It is why the lesson's sret table is dated and targeted.
  • System V AMD64 psABI ↗ (the project's README links the latest PDF; version 1.0 of March 12, 2025 when checked), ch. 3 "Low Level System Information" → §3.2 "Function Calling Sequence" → §3.2.3 "Parameter Passing" → "Returning of Values". Integer results go back in %rax and %rdx. For a result classed MEMORY, the caller provides the space and passes its address in %rdi as a hidden first argument. That hidden argument is what LLVM IR spells sret.

Books

  • Programming Rust, 2nd ed., ch. 4 "Ownership and Moves" → "Moves" → "More Operations That Move" (p. 90). Its Returning values from a function paragraph: Vec::new() returns the vector itself, not a pointer to it, and a move copies only the three-word header while the buffer stays put. It says the generated code often stores the value directly where it belongs, and "often" is the right word (read with care, the RVO entry).
  • Rust for Rustaceans, ch. 11 "Foreign Function Interfaces" → "Crossing Boundaries with extern" → "Calling Conventions" (p. 198): what a calling convention decides (the stack frame, arguments in registers or on the stack, the return address, which registers the caller gets back intact), and that Rust's own is not standardized and may change between compiler versions. It says a bare extern means extern "C", which still holds, but rustc 1.98.0 now warns on it: missing_abi, "extern declarations without an explicit ABI are deprecated". The library's chapter-by-chapter map links both sections to lessons.
  • Rust in Action (Tim McNamara, Manning 2021):
  • ch. 2 "Language foundations" → §2.9 "Creating grep-lite", sidebar "Navigating Rust's rich collection of string types": a variable of type str is refused because the compiler lays out fixed-size variables in the function's stack frame.
  • ch. 6 "Memory" → §6.3 "Providing programs with memory for their data" → 6.3.1 "The stack": data goes on the stack only when its size is known at compile time, hence the chapter's rule of thumb to prefer Sized types. The chapter's pointer claims are run in Rust in Action, chapter 6.
  • ch. 11 "Kernel" → §11.2 "Fledgeos-0: Getting something working" → 11.2.3 "Source code listings": _start is marked extern "C" because Rust does not define its calling convention, and without it the boot process may fail.
  • Effective Rust (David Drysdale, O'Reilly 2024, free online ↗), ch. 2 "Traits" → Item 12 "Understand the trade-offs between generics and trait objects" → "Trait Object Safety". The same reasoning one step on: code holding a trait object that calls x.clone() has to reserve room for the result without knowing its size. So a method returning Self blocks dyn, unless it carries where Self: Sized. Run on 1.98.0, fn make_copy(&self) -> Self; is E0038, "references the Self type in its return type", and adding where Self: Sized lets &dyn Stamp compile. One sentence to read with care.

The Book

The Reference, std and the error index

  • Sized ↗: every type parameter has an implicit Sized bound, and a trait's own Self is the one exception. That exception is why Clone has to write : Sized on its declaration.
  • Clone ↗: the declaration at the top of the page, pub trait Clone: Sized, with fn clone(&self) -> Self.
  • The Reference — Dynamically sized types ↗: which types are DSTs, and what metadata a pointer to each carries. Its note lists variables, function parameters, const items and static items as places that must be Sized. Return types are not on that list. The rule for them is in rustc's own note on E0277: "the return type of a function must have a statically known size" (rustc 1.98.0).
  • The Reference — Traits → Dyn compatibility ↗: Sized must not be a supertrait, which is how Clone: Sized rules out dyn Clone, and a method bounded where Self: Sized is non-dispatchable, so a trait can keep a -> Self method and still be used as dyn.
  • The Reference — Type layout → The Rust representation ↗: beyond what soundness needs, the default layout guarantees nothing. Like the calling convention, it is the compiler's choice.
  • The Reference — Functions → extern function qualifier ↗: a function with no extern gets the "Rust" ABI.
  • fn → ABI compatibility ↗: the few ABI promises Rust does make, for Rust-to-Rust calls through function pointers. Same size and alignment is not enough: its example is i32 and f32, which may travel in different registers.
  • The Rustonomicon — Exotically Sized Types → DSTs ↗: a DST exists only behind a pointer, and a custom one is built by unsizing a generic struct.
  • rustc book — noop_method_call ↗: the lint behind .clone() on a &str. Its example is a &Foo whose Foo is not Clone, and its note says the call copies the reference. The note gives no reason why the type is not Clone.
  • Error index. Run on 1.98.0, each signature produces a different set:
  • fn f(s: &str) -> str { *s } is E0277 ↗ and E0507 ↗. The E0277 page is the general trait not implemented explanation with no Sized example, so rustc's note says more than the index does. E0507 is the body's *s, a move out of a borrow.
  • fn f(v: &[i32]) -> [i32] { *v } is E0277 and E0508 ↗, a move out of a slice.
  • fn f() -> dyn Display is E0746 ↗, whose page gives both fixes, impl Trait for one type and Box<dyn Trait> for several.
  • Returning &s of a local String is E0515 ↗, whose fix is to return the owned value instead.

The machine: calling conventions and sret

  • System V AMD64 psABI ↗, §3.2.3 "Parameter Passing" (above, and in the four). The classification it applies before "Returning of Values" also says a C++ object that is non-trivial for the purpose of calls is passed by invisible reference. clang 21 shows both rules on x86_64-apple-darwin: struct { long a, b; } comes back as { i64, i64 } in two registers, a 24-byte struct through sret, and a one-byte C++ struct with a user-written copy constructor through sret too. rustc 1.98.0 follows the C rule under extern "C": a #[repr(C)] pair of u64 comes back as { i64, i64 }.
  • Apple — Writing 64-bit Intel code for Apple Platforms ↗: macOS on x86-64 typically follows the System V psABI above, and the page lists where Apple's compilers diverge. The lesson's measurements were taken on this target.
  • LLVM Language Reference — Parameter Attributes ↗, sret(<ty>): the pointer parameter is the address of the structure that is the source program's return value, the caller must make it valid, the function returns void, and <ty> is the in-memory type. That type is the [24 x i8] in the lesson's sret([24 x i8]).
  • Aria Desires, Notes on Type Layouts and ABIs in Rust (2018) ↗, §3.4 "Calling Conventions" and §3.4.1–3.4.2: registers or the stack, caller-saved and callee-saved registers, then one C declaration lowered for 32-bit and 64-bit System V. The same one-int struct comes back through an implicit pointer on x86 and in rax on x64. clang 21 agrees: i386-unknown-linux-gnu returns a four-byte struct through sret, x86_64 as an i32. One sentence to read with care.

Unsized arguments, and why returns stayed sized

  • RFC 1909 — Unsized Rvalues ↗, "Detailed design" → "Unsized Rvalues - language". The RFC lets locals and arguments be unsized, but function calls must still return a Sized type, because an unsized return would need the called function to allocate in the caller's stack frame. The lesson makes the same point from the caller's side.
  • rust-lang/rust#48055 ↗, the tracking issue for unsized_locals and unsized_fn_params: open since 2018, with its blocking bugs and unresolved questions. It says nothing about return values. Run on nightly 1.100.0 (2026-08-27) with #![feature(unsized_fn_params)]: fn take(s: str) -> usize compiles and runs, and fn give(b: Box<str>) -> str is still E0277 with the same return-type note.
  • rust-lang/rust#111942 ↗, Tracking Issue for removing unsized_locals. On that nightly, #![feature(unsized_locals)] is E0557, "feature has been removed", removed in 1.89.0.

Articles and answers

Video

Read with care

  • A common explanation of the noop_method_call warning. It says the warning explains that str is not Clone because it is unsized, and that there is therefore no str::clone for auto-deref to find. The conclusion is right and the mechanism is not. rustc 1.98.0's note only says str does not implement Clone, so calling clone on &str copies the reference. It gives no reason; the reason is Clone: Sized. And no dereference is involved. For name: &str the first receiver type tried is &str itself, the receiver of a str::clone(&self), so such a method would have been found first. The reference's own clone is found one step later, at &&str, by adding a & (A common explanation, checked; The dot takes the first receiver that fits).
  • The Book, ch. 20.4 — Returning Closures, first sentence: closures are represented by traits, so you cannot return them directly. The next listing does return one directly, through impl Fn. Run on 1.98.0, fn adder(n: i64, m: i64, k: i64) -> impl Fn(i64) -> i64 hands back a 24-byte closure (size_of_val) through sret([24 x i8]), the same hidden slot as a String. A closure has a concrete type with a size and no name (A closure is a struct the compiler wrote). What cannot be returned is dyn Fn(i32) -> i32, which is E0746.
  • "Rust has RVO", in both Stack Overflow answers above. The 2015 answer says Rust has return value optimization, and the 2025 one that RVO and NRVO are just how the language works. The hidden pointer is the calling convention and is there in every build. Building the value straight into the caller's slot is an optimization, not a rule. Run on 1.98.0, fn build() -> [u64; 64] that fills a local a and returns it: at opt-level=0 the IR fills its own alloca for a, then memcpys 512 bytes into the sret slot, and at opt-level=3 it writes into the caller's slot directly. Programming Rust's "often" is the accurate word.
  • Effective Rust, Item 12. Traits that require Copy or Clone are said to lose object safety because those traits return Self. Copy has no method at all. For trait Foo: Clone, rustc 1.98.0 names a different rule: E0038, "because it requires Self: Sized", the supertrait Clone carries. The reserve room for the result explanation is right for a -> Self method of your own. Current docs call object safety dyn compatibility.
  • pretzelhammer's transcripts. The E0277 output under "Sized in Generics" ends with a help line that unsized locals are gated as an unstable feature. That feature was removed in 1.89.0 (above), and 1.98.0 prints "function arguments must have a statically known size, borrowed types always have a known size" instead. The claims hold. The text has changed.
  • The accepted answer on Why is the Sized bound necessary writes trait objects the pre-2018 way, let x: Display and Box<SomeTrait>. On edition 2021 or 2024, Box<Display> is E0782, "expected a type, found a trait". Read Display there as dyn Display.
  • Aria Desires, §3.4.1. One sentence has the roles reversed: it says the caller puts the return value in the register before returning control to the callee. The callee places it before returning to the caller: psABI "Returning of Values", and mov rax, rdi in three_numbers in the lesson's assembly.

See also

Po polsku

Lista lektur do lekcji o zwracaniu przez wartość, sprawdzona 2026-09-17. Najkrócej: w Programming Rust rozdział 13, sekcje „Sized” i „Clone” (jedno zdanie tłumaczy, czemu Clone wymaga Sized), w Rust for Rustaceans sekcja o typach o dynamicznym rozmiarze, w Reference zdanie, że ABI Rusta nie daje żadnych gwarancji stabilności, a w specyfikacji System V AMD64 reguła: wywołujący rezerwuje miejsce na wynik i przekazuje jego adres jako ukryty pierwszy argument (sret w LLVM). RFC 1909 pozwala na argumenty bez rozmiaru, ale wynik funkcji nadal musi mieć rozmiar.

Czytaj uważnie: popularne wyjaśnienie ostrzeżenia noop_method_call („nie ma str::clone dla auto-deref”) ma dobry wniosek i zły mechanizm, bo żadnej dereferencji tu nie ma. The Book pisze, że domknięcia nie da się zwrócić bezpośrednio, a impl Fn zwraca je właśnie przez wartość. RVO w Ruscie to optymalizacja, nie gwarancja: przy opt-level=0 wartość jest kopiowana (memcpy) do miejsca wywołującego.

Szukaj po polsku: zwracanie przez wartość · konwencja wywołań · typy o dynamicznym rozmiarze · rust sret · System V AMD64 return value · rust RVO NRVO