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:
clonereturnsSelf, a function cannot return an unsized value, soCloneextendsSized. - 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
Sizednames 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 plainfngets, offers no stability guarantees. It is why the lesson'ssrettable 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
%raxand%rdx. For a result classed MEMORY, the caller provides the space and passes its address in%rdias a hidden first argument. That hidden argument is what LLVM IR spellssret.
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 bareexternmeansextern "C", which still holds, but rustc 1.98.0 now warns on it:missing_abi, "externdeclarations 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
stris 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
Sizedtypes. 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":
_startis markedextern "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 returningSelfblocksdyn, unless it carrieswhere Self: Sized. Run on 1.98.0,fn make_copy(&self) -> Self;isE0038, "references theSelftype in its return type", and addingwhere Self: Sizedlets&dyn Stampcompile. One sentence to read with care.
The Book¶
- ch. 20.3 — Dynamically Sized Types and the
SizedTrait ↗ (ch. 19 "Advanced Features" in the 2019 No Starch print edition, p. 441):stris a DST, so there is no variable or argument of typestr, and the rule is to keep a DST behind a pointer,&str,Box<str>orRc<str>. Return types are not mentioned. TheSizedbound on every genericT, and?Sized, are in the same section. - ch. 10.2 — Returning Types That Implement Traits ↗:
-> impl Trait, the fix for-> dyn Traitwhen there is one concrete type, and why it stops working with two. - ch. 4.1 — Return Values and Scope ↗: returning a value moves ownership to the caller. The ownership side of what the lesson measures in bytes.
- ch. 20.4 — Returning Closures ↗:
impl FnandBox<dyn Fn>as return types. Its opening sentence is on the read with care list.
The Reference, std and the error index¶
Sized↗: every type parameter has an implicitSizedbound, and a trait's ownSelfis the one exception. That exception is whyClonehas to write: Sizedon its declaration.Clone↗: the declaration at the top of the page,pub trait Clone: Sized, withfn 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,
constitems andstaticitems as places that must beSized. Return types are not on that list. The rule for them is in rustc's own note onE0277: "the return type of a function must have a statically known size" (rustc 1.98.0). - The Reference — Traits → Dyn compatibility ↗:
Sizedmust not be a supertrait, which is howClone: Sizedrules outdyn Clone, and a method boundedwhere Self: Sizedis non-dispatchable, so a trait can keep a-> Selfmethod and still be used asdyn. - 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 →
externfunction qualifier ↗: a function with noexterngets 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 isi32andf32, 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&FoowhoseFoois notClone, and its note says the call copies the reference. The note gives no reason why the type is notClone. - Error index. Run on 1.98.0, each signature produces a different set:
fn f(s: &str) -> str { *s }isE0277↗ andE0507↗. TheE0277page is the general trait not implemented explanation with noSizedexample, so rustc's note says more than the index does.E0507is the body's*s, a move out of a borrow.fn f(v: &[i32]) -> [i32] { *v }isE0277andE0508↗, a move out of a slice.fn f() -> dyn DisplayisE0746↗, whose page gives both fixes,impl Traitfor one type andBox<dyn Trait>for several.- Returning
&sof a localStringisE0515↗, 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 throughsret, and a one-byte C++ struct with a user-written copy constructor throughsrettoo. rustc 1.98.0 follows the C rule underextern "C": a#[repr(C)]pair ofu64comes 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 returnsvoid, and<ty>is the in-memory type. That type is the[24 x i8]in the lesson'ssret([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-
intstruct comes back through an implicit pointer on x86 and inraxon x64. clang 21 agrees:i386-unknown-linux-gnureturns a four-byte struct throughsret,x86_64as ani32. 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
Sizedtype, 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_localsandunsized_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) -> usizecompiles and runs, andfn give(b: Box<str>) -> stris stillE0277with the same return-type note. - rust-lang/rust#111942 ↗, Tracking Issue for removing
unsized_locals. On that nightly,#![feature(unsized_locals)]isE0557, "feature has been removed", removed in 1.89.0.
Articles and answers¶
- pretzelhammer, Sizedness in Rust (2020) ↗, sections "Sizedness" and "
Sizedin Generics": only sized values are passed around by value. A trait may declarefn method(self);, but a default body, or an impl forstr, is refused. All three claims hold on 1.98.0. Its transcripts are from an older compiler (read with care). - Stack Overflow — Why is the
Sizedbound necessary in this trait? ↗ (question score 87, accepted answer 223, last edited 2018): a default method that returnsSelfneedsSelf: Sized, because a trait'sSelfis notSizedby default, andwhere Self: Sizedon the one method is the narrower fix. Both run on 1.98.0: the default method is twoE0277, and thewhereclause compiles. - Stack Overflow — Can I efficiently return an object by value in Rust? ↗ (2015; top answer 25, last edited 2018): release assembly for
fn initialize() -> Vec<i32>writing the newVecthrough the address inrdi. rustc 1.98.0 atopt-level=3still does: threemovs, into[rdi],[rdi + 8]and[rdi + 16]. - Stack Overflow — Why is "return by value" idiomatic in Rust (as opposed to out parameters)? ↗ (2025; top answer 73): C++'s path from copying return values to copy elision and move constructors, and why Rust code returns by value while the calling convention still uses return slots underneath.
Video¶
- Jon Gjengset, Crust of Rust: Dispatch and Fat Pointers ↗ (2021, 2 h 13 min). Chapters worth jumping to: 0:27:13 The Sized Trait, 0:39:34 Sizing Unsized Types, 1:43:03 Dynamically Sized Types, 1:53:41 Box([u8]) vs. Vec(u8), 1:55:18 dyn Fn() vs. fn() vs. impl Fn(). Also on the
ToOwnedreading list.
Read with care¶
- A common explanation of the
noop_method_callwarning. It says the warning explains thatstris notClonebecause it is unsized, and that there is therefore nostr::clonefor auto-deref to find. The conclusion is right and the mechanism is not. rustc 1.98.0's note only saysstrdoes not implementClone, so callingcloneon&strcopies the reference. It gives no reason; the reason isClone: Sized. And no dereference is involved. Forname: &strthe first receiver type tried is&stritself, the receiver of astr::clone(&self), so such a method would have been found first. The reference's owncloneis 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) -> i64hands back a 24-byte closure (size_of_val) throughsret([24 x i8]), the same hidden slot as aString. A closure has a concrete type with a size and no name (A closure is a struct the compiler wrote). What cannot be returned isdyn Fn(i32) -> i32, which isE0746. - "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 localaand returns it: atopt-level=0the IR fills its ownallocafora, thenmemcpys 512 bytes into thesretslot, and atopt-level=3it writes into the caller's slot directly. Programming Rust's "often" is the accurate word. - Effective Rust, Item 12. Traits that require
CopyorCloneare said to lose object safety because those traits returnSelf.Copyhas no method at all. Fortrait Foo: Clone, rustc 1.98.0 names a different rule:E0038, "because it requiresSelf: Sized", the supertraitClonecarries. The reserve room for the result explanation is right for a-> Selfmethod of your own. Current docs call object safety dyn compatibility. - pretzelhammer's transcripts. The
E0277output under "Sizedin 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
Sizedbound necessary writes trait objects the pre-2018 way,let x: DisplayandBox<SomeTrait>. On edition 2021 or 2024,Box<Display>isE0782, "expected a type, found a trait". ReadDisplaythere asdyn 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, rdiinthree_numbersin the lesson's assembly.
See also¶
- Returned by value: the caller needs a size, the lesson, with every error and lints
- Some types have no size: step 2 of the
ToOwnedpath,Sizedand the wide pointer Clonehands backSelf: step 4,Clone: Sizedand why&TisClonestris unsized: the size belongs to the value- The call stack: the region a call reserves, and the footnote everyone skips on registers
- LLVM: the part of rustc that is not Rust: how to read a
defineline and its attributes - Returning a trait and The third owned form:
Box<str>: two of the lesson's fixes - The C ABI: the
extern "C"side, where the calling convention is a published promise - Helpful resources for the
ToOwnedpath and Books: the neighbouring reading lists - Drawing
sret: the psABI rule and the LangRef definition above, drawn and checked
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