Pre-RFC: Homogenize Move Semantics for All Types — Eliminating Copy/Clone Distinction to Reduce Cognitive Load

Hello everyone,

I'm a Rust developer with limited experience in large-scale projects but significant time working on complex codebases in the language. I deeply admire Rust's design, especially its ownership model that provides memory safety without GC and predictable performance. However, I've consistently encountered a recurring friction point: the distinction between Copy and non-Copy types, which introduces unnecessary constant cognitive load—even in everyday code.

This proposal does not aim to "break" Rust but to evolve it toward better ergonomics in future editions (e.g., a post-2026 edition or Rust 2.0), while keeping its core pillars intact: unique ownership, strict borrow checker, deterministic drop, and zero-cost abstractions. It builds on previous discussions in this forum, such as "Ownership: Move by default" (2019) Ownership: Move by default, which explicitly mentions the cognitive load caused by needing to know whether a type implements Copy to predict behavior in assignments and parameter passing.

The Problem: Two Contradictory Rules and Constant Cognitive Load

In current Rust, there are two contradictory semantic rules for let y = x; or passing x by value:

  1. For types implementing Copy (primitives like i32, small structs without heap/Drop): x remains alive and usable afterward — it's an implicit bitwise copy.
  2. For non-Copy types (String, Vec, most complex structs): x becomes invalid — it's a move of ownership.

This duality forces a constant "mental pause": in every assignment or call, you must recall or check if the type is Copy. In large projects (millions of lines, like Servo or Firefox), this accumulates cognitive fatigue, subtle errors, and noise during refactors. For example:

let a: i32 = 42;
let b = a;          // a remains alive (rule 1)
println!("{}", a);  // OK

let s: String = "hello".to_string();
let t = s;          // s becomes invalid (rule 2)
println!("{}", s);  // Error: use of moved value

Beginners get confused transitioning from primitives to heap types; experts waste time inspecting traits/docs in generics or legacy code. This subtly violates "zero-cost abstractions" by imposing unnecessary mental cost in 90% of trivial cases.

The Proposal: One Single Universal Rule — "Move Always Invalidates the Original"

Apply one single semantic rule for all types:

let y = x; or passing x by value is always a move: transfers ownership, invalidates x. No exceptions based on type. The compiler optimizes moves of trivial types (no heap/Drop) to internal bitwise copies, but the visible semantics remain uniform.

Examples in this "homogeneous Rust":

let a: i32 = 42;
let b = a;          // Move: a becomes invalid
println!("{}", a);  // Error: use of moved value

let s: String = "hello".to_string();
let t = s;          // Move: s becomes invalid
println!("{}", s);  // Error: use of moved value
  • For trivial types: Compiler uses NRVO/RVO to optimize to bitwise copy (identical performance to current Copy).
  • For heap types: Transfers pointer/len/cap (O(1), like current move).
  • No public Copy trait — behavior is automatic and predictable.

When you need to use the original afterward: Use explicit references (&x), which is already idiomatic for sharing.

Practical example (constant configuration):

let port: i32 = 8080;
setup_server(&port);     // Immutable borrow: port remains alive
log_port(&port);         // Another borrow
println!("{}", port);    // OK

Key Advantages

  • Drastic reduction in cognitive load: One single rule to learn and apply. No more "it depends on the type" — seamless flow in large code without mental pauses.
  • Consistency and teachability: Rust becomes more intuitive for beginners (one rule from day 1) and experts (local predictable reasoning).
  • Enhanced safety: Forces intentional thinking about ownership when sharing, reducing unintentional copies in structs that "used to be Copy but grew".
  • Predictable performance: Defaults to O(1) moves. Existing optimizations (NRVO) intact — zero overhead.
  • Compatibility: Implementable in a future edition (opt-in via flag, transpile legacy code by adding explicits where needed).

In large projects, 95% of assignments are temporary or final — you don't use the original afterward, so little code change. When you do, & is trivial.

Possible Criticisms and Anticipated Responses

  1. Criticism: Loss of Ergonomics for Primitive Types Criticism: "Now I can't use an i32 after moving it without using &. This breaks the simplicity and ergonomics of primitive types." Anticipated Response: In most cases, primitive types like i32 are moved and not needed again afterward. When you do need to reference them multiple times, you simply use & (as in &i32). Ergonomics are not lost; they become consistent with the handling of other types. This change improves long-term predictability. Moreover, the cognitive load of remembering whether a type is Copy or not is completely eliminated. In real-world large projects, primitives are rarely used after a move in ways that require the original to remain alive without borrowing.

  2. Criticism: Introduces Overhead from References Criticism: "Explicitly using & for primitive types becomes annoying and leads to unnecessary references." Anticipated Response: In practice, explicit references in Rust are lightweight and already idiomatic (e.g., &str or &[T]). Transitioning primitives to the same pattern creates uniformity across the language. The overall consistency gain in large codebases far outweighs the minor inconvenience of adding & in the few cases where sharing is needed. IDEs and the borrow checker already suggest these fixes automatically.

  3. Criticism: Loss of Fine-Grained Control over Data Movement Criticism: "Rust provides very fine control over data movement with Copy and Move. This is lost with the uniform model." Anticipated Response: Fine-grained control over data movement is not lost—everything is still a move under the proposed model. The difference is that all types now follow the same rule, reducing confusion. When explicit control over references or sharing is needed, & and &mut remain available. Removing Copy does not reduce control; it makes the model more predictable and easier to reason about.

  4. Criticism: Complications for Types with Drop or Dynamic Memory Criticism: "Moving types that implement Drop (like String, Vec, etc.) could become problematic since they would now move uniformly." Anticipated Response: This behavior already exists in Rust today for non-Copy types. Types with Drop simply transfer ownership during a move, ensuring no resource duplication. Memory safety is preserved exactly as before because unique ownership is maintained. There is no implicit copying of heap data—only ownership transfer.

  5. Criticism: Potential Performance Impacts Criticism: "Always moving types could reduce performance, especially with large data structures." Anticipated Response: The model introduces no performance penalty. For trivial types (like i32), the compiler already optimizes moves using NRVO/RVO, eliminating any copy overhead—generated assembly remains identical. For complex types (String, Vec), moves transfer only pointers/length/capacity (O(1)), exactly as today. Performance remains predictable and optimal, with zero additional runtime cost.

  6. Criticism: Backward Compatibility Breakage Criticism: "This proposal is not backward-compatible with current Rust and would break the ecosystem." Anticipated Response: The proposal is designed for a future Rust edition (post-2024 or Rust 2.0), allowing a smooth transition with a compatibility mode for legacy code. Migration could be gradual, with compiler flags or automatic fixes (e.g., inserting & where legacy code expects Copy behavior). Existing crates and FFI remain unaffected in compat mode.

  7. Criticism: Breaks Rust's Ownership and Borrowing Model Criticism: "Rust's ownership and borrowing system is one of its greatest strengths. Doesn't this interfere with it?" Anticipated Response: The proposal does not modify the core ownership or borrowing model—it strengthens it by applying a single consistent rule for moves. Borrowing (& and &mut) works exactly as before. The change only simplifies type behavior during moves, removing exceptions and making ownership reasoning more intuitive without altering safety guarantees.

  8. Criticism: Potential Complexity in Generics Criticism: "Introducing T: Movable for generics could add unnecessary complexity or surprises." Anticipated Response: Actually, this simplifies generics. The distinction between T: Copy and T: Clone disappears, reducing bound clutter. Most generic functions will no longer need explicit Copy or Clone bounds. The default Movable trait (applicable to all types) makes abstractions cleaner and more predictable.

  9. Criticism: No Real Benefits Criticism: "Rust is already very consistent in memory safety and ownership—why change something that works well?" Anticipated Response: While Rust is excellent, the Copy/non-Copy distinction remains a persistent source of cognitive friction. Programmers constantly need to recall or check whether a type is Copy, even in trivial code. This proposal eliminates that mental overhead, significantly improving development flow in large projects. Consistency and predictability are enhanced without sacrificing any of Rust's core strengths.

  10. Criticism: Breaks Zero-Cost Abstractions Criticism: "The transition cost and behavior changes could violate the principle of zero-cost abstractions." Anticipated Response: The model preserves zero-cost abstractions completely. Optimizations like NRVO and RVO remain fully applicable. The change removes cognitive cost (having to think about type categories on every move) while keeping runtime cost identical. In fact, the simplified model can improve code clarity and maintainability.

Conclusion

This proposal makes Rust more powerful by removing a real source of friction without altering its fundamental strengths. It is implementable via a compiler fork (with an MVP using macros for prototyping) and scalable to a formal RFC if there is interest.

What do you think? Have you experienced this cognitive load in large projects? Ideas for refinement?

Thanks for reading, jac

This is not a full response, but we have in some sense done an experiment with this already with std::ops::Range, a type frequently expected to be Copy and in practice not (because it doubles as its own iterator). Complaints about this over the years have accreted to the point where std::range::Range is now under development to replace it. Anecdotally, I tried rewriting a colleague’s code to use std::ops::Range rather then a pair of indexes and found the result much less natural, due to having to insert extra clone() calls—not just reference-taking, but even that would have been unnecessary noise compared to the index version.

So to say it clearly: no, I don’t think eliminating implicit copies via Copy makes Rust a better language. A more consistent one, yes, but consistency is not the only goal for a language.

(And to put a final bow on it all, the current mechanism by which immutable references can be passed around without extra syntax is…Copy. References are special in many ways, but I don’t believe this is one of them.)

13 Likes

Did you generate this with an LLM? It contains numerous factual errors and unusual wording/structuring. Either way, I do not think you proofread it.

18 Likes

Evidence would help. Can you show any examples where people ended up having a hard time understanding the code because there wasn't an explicit .clone()? Has anyone ever wished they had to write x[i.clone()] + y[i] instead of x[i] + y[i]? Have people really wanted their non-mut reference to move instead of clone?

Yes, it's a common learner's question, but something this impactful definitely wouldn't happen without strong evidence of it causing persistent confusion. (And I just don't see that.)

11 Likes

I didn't bother to read the whole thing because it's clear that it's LLM-generated. I'm not going to waste my time reading a lengthy post when the poster couldn't be bothered to write it themself.

11 Likes

Hello again!

Thank you to everyone who took the time to read my pre-RFC.

  1. I apologize if the writing style I used felt unusual or overly polished. English is not my native language, and yes, I used an LLM to help improve clarity, structure, and readability. If I had posted it using only my own words, it would surely have been extraordinarily difficult to understand.

  2. I completely understand the skepticism and rejection — it’s normal. The proposal represents a radical change, and it is deep enough that it could be interpreted as the development or rewriting of an entirely new language.

  3. And indeed, I do not have, at least not in my hands, solid evidence of widespread confusion or problems in real-world code. There are, however, some reported cases, and mine is one of them.

  4. My idea is: simpler is better. That’s what I wanted to convey with this post — but we all already know that!

For me, it’s easier to develop when I don’t have to think about how to use the language itself. That gives me more time and mental space to move the project forward, without having to explicitly review or second-guess what I’ve done.

  1. And of course, it was never my intention to waste anyone’s valuable time… so you know the drill — back to work!

  2. Thank you for the responses, and above all, thank you for creating, extending, developing, and maintaining “the best language” I’ve used since I began my journey into the world of programming back in 1986.

Greetings from Spain, and Happy Hacking! jac.

If you wanted to move forward here, I'd suggest collecting the code where the confusion happens from a bunch of these cases. Then you can work to get to "people agree that these are confusing" first, and then later talk about possible solutions to help reduce that confusion.

Focusing on the examples allows for more creativity in how to fix it, and helps lessen the solution-based pushback you're getting.

6 Likes

Meta:

We would rather read your own words, no matter how hard you think it will be to understand.

We would rather read your own words in your native language than trust that a machine has correctly translated you into English.

17 Likes

Absolutely agreed. And not only that, Spanish is such a widespread language, there probably are many native Spanish speakers here on this forum. If the proposal sounds interesting or worth discussing, i can help translating or making any clarifications if needed :slight_smile:

Besides, it's totally normal to feel uneasy about writing with "your own words" on a foreign-speaking forum, but it's worth the effort to try and do it. Written communication is a skill. And just as any other skill, there's only one sure way of getting better at it!

4 Likes

I think using machine translators is fine and preferable to posting in foreign languages. But when using machine translation, I recommend using a dedicated translation tool like Google Translate or DeepL, as it will produce text that is much closer to what you said in the original. Please use these tools instead of a large language model like ChatGPT, as those lead to way more rewording and change of tone.

9 Likes