<i>>Sometimes, when changing an enum, we want to keep parts of the old value. Use mem::replace to avoid needless clones.</i><p><pre><code> use std::mem;
enum MyEnum {
A { name: String, x: u8 },
B { name: String }
}
fn a_to_b(e: &mut MyEnum) {
// we mutably borrow `e` here. This precludes us from changing it directly
// as in `*e = ...`, because the borrow checker won't allow it. Therefore
// the assignment to `e` must be outside the `if let` clause.
*e = if let MyEnum::A { ref mut name, x: 0 } = *e {
// this takes out our `name` and put in an empty String instead
// (note that empty strings don't allocate).
// Then, construct the new enum variant (which will
// be assigned to `*e`, because it is the result of the `if let` expression).
MyEnum::B { name: mem::replace(name, String::new()) }
// In all other cases, we return immediately, thus skipping the assignment
} else { return }
}
</code></pre>
Don't get me wrong, I think Rust is an incredibly impressive language, but this is <i>nuts.</i> For the first time ever, I had a moment of appreciation for the "simple clarity" of C++11 move constructors. If it weren't for the comments and the documentation[1] I wouldn't have had the slightest clue what this code is doing (a hack to fool the borrow checker while allowing for Sufficiently Smart Compilation to a no-op[2] ... I think).<p>This is a good example of the main conceptual aspect of Rust where I feel it could use improvement.[3] A lot of its features marry very high-level concepts (like algebraic data types) to exposed, low-level implementations (like tagged unions). Now, there's nothing wrong with the obvious choice to implement ADTs as tagged unions, but the nature of Rust as a language that exposes low-level control over allocation and addressing, in combination with the strictures of the borrow checker, means enums and other high-level features live in a sort of uncanny valley, falling short of either the high-level expressiveness of ADTs or the low-level flexibility of tagged unions (without expert-level knowledge or using `unsafe`).<p>Similarly, the functional abstractions almost feel like a step backwards from the venerable C++ <algorithm>, abstraction and composition-wise. Very nitty-gritty, low-level implementation details leak out like a sieve — you can't have your maps or folds without a generous sprinking of `as_slice()`, `unwrap()`, `iter()` / `iter_mut()` / `into_iter()` and `collect()` everywhere, although at least for this case you would be able to figure out the idioms by reading the official docs. But nevertheless it seems like reasonable defaults could be inferred from context (with the possible exception of `collect()` since it allocates), while still allowing explicitness as an option when you want low-level control over the code the compiler generates.<p>In this case, enums are a language-level construct, not a library, so the borrow-checker really shouldn't be rejecting reasonable patterns. It (legal to alias between the structural <i>and</i> nominal common subsequence of several members of an enum) should be the default behavior and not require an nonobvious, unreadable hack such as the above. At the very least that behavior (invaluable for many low-level tasks) should be easy to opt in to with e.g. an attribute.[4]<p>[1] <a href="https://doc.rust-lang.org/std/mem/fn.replace.html" rel="nofollow">https://doc.rust-lang.org/std/mem/fn.replace.html</a><p>[2] Or rather since it is a tagged union, in MASMish pseudocode something like<p><pre><code> jnz x_nonzero
mov MyEnum.B, e.tag
x_nonzero:
ret
</code></pre>
Although it could still be a no-op depending on what else Sufficiently Smart Compiler/LLVM inlines.<p>[3] And I'm not saying I have the solution or even that all-around-better solutions exist.<p>[4] Something like<p><pre><code> #![safe_alias_common_subsequence(structural)]
#![safe_alias_common_subsequence(nominal_and_structural)]</code></pre>