Description
Code
Compare the following three commits.
-
https://codeberg.org/mcc/nameless-experimental-lisp/src/commit/5bfbda4cb2e1a9760d0048bdf7d37e2fb3bcadf7 (branch
2025-06-14
commit5bfbda4cb
). This branch targets edition 2021, and compiles. -
https://codeberg.org/mcc/rs-bug/src/commit/1e5b33af6e364d39bffffc015f5541f6a302cde2 (branch
nel-move-error-msg
, commit1e5b33af
— notice this is a different git repo from other two commits). This branch targets edition 2024, and code unchanged from commit 1 does not compile. -
https://codeberg.org/mcc/nameless-experimental-lisp/src/commit/44e9257cff43c8db54301bff75dc8f34ede09170 (branch
2025-06-14
commit44e9257cf
). Compared to commit 2, you will see various lines changed (all lines unchanged commit 1->2) that cause the program to build again without errors/warnings.
When testing any of the above, it is adequate to just run cargo build --release
.
Current output
When building "commit 2" above, the (very confusing) error message
modifiers may only be written when the default binding mode is `move`
appears ten separate times in the build output:
mcc@Anthy:~/work/r/nil-macro/dep/lisp0$ cargo build --release
Compiling experimental-lisp-0 v0.1.4-beta (/home/mcc/work/r/nil-macro/dep/lisp0)
error: binding modifiers may only be written when the default binding mode is `move`
--> src/memory.rs:763:31
|
763 | let MemCell::Array(_, ref mut v) = self.memory.cell_mut($to.clone()) else { unreachable!(); };
| ^^^^^^^^^ occurs within macro expansion
...
777 | let addr = handle_extend!(new_array, handle, upto, idx, true, true);
| -------------------------------------------------------- in this macro invocation
|
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
= note: this error originates in the macro `handle_extend` (in Nightly builds, run with -Z macro-backtrace for more info)
help: make the implied reference pattern explicit
|
763 | let &mut MemCell::Array(_, ref mut v) = self.memory.cell_mut($to.clone()) else { unreachable!(); };
| ++++
error: binding modifiers may only be written when the default binding mode is `move`
--> src/memory.rs:763:31
|
763 | let MemCell::Array(_, ref mut v) = self.memory.cell_mut($to.clone()) else { unreachable!(); };
| ^^^^^^^^^ occurs within macro expansion
...
786 | handle_extend!(new_array, new_ast, 0, self.memory.array_len(new_ast.clone()), false, false);
| ------------------------------------------------------------------------------------------- in this macro invocation
|
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
= note: this error originates in the macro `handle_extend` (in Nightly builds, run with -Z macro-backtrace for more info)
help: make the implied reference pattern explicit
|
763 | let &mut MemCell::Array(_, ref mut v) = self.memory.cell_mut($to.clone()) else { unreachable!(); };
| ++++
error: binding modifiers may only be written when the default binding mode is `move`
--> src/memory.rs:763:31
|
763 | let MemCell::Array(_, ref mut v) = self.memory.cell_mut($to.clone()) else { unreachable!(); };
| ^^^^^^^^^ occurs within macro expansion
...
795 | handle_extend!(new_array, handle, upto, self.memory.array_len(handle.clone()), false, true);
| ------------------------------------------------------------------------------------------- in this macro invocation
|
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
= note: this error originates in the macro `handle_extend` (in Nightly builds, run with -Z macro-backtrace for more info)
help: make the implied reference pattern explicit
|
763 | let &mut MemCell::Array(_, ref mut v) = self.memory.cell_mut($to.clone()) else { unreachable!(); };
| ++++
error: binding modifiers may only be written when the default binding mode is `move`
--> src/memory.rs:800:29
|
800 | let MemCell::Array(_, ref mut v) = self.memory.cell_mut(new_array) else { unreachable!(); };
| ^^^^^^^ binding modifier not allowed under `ref mut` default binding mode
|
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
note: matching on a reference type with a non-reference pattern changes the default binding mode
--> src/memory.rs:800:11
|
800 | let MemCell::Array(_, ref mut v) = self.memory.cell_mut(new_array) else { unreachable!(); };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&mut _`
help: remove the unnecessary binding modifier
|
800 - let MemCell::Array(_, ref mut v) = self.memory.cell_mut(new_array) else { unreachable!(); };
800 + let MemCell::Array(_, v) = self.memory.cell_mut(new_array) else { unreachable!(); };
|
error: binding modifiers may only be written when the default binding mode is `move`
--> src/memory.rs:804:29
|
804 | let MemCell::Array(_, ref mut v) = self.memory.cell_mut(handle) else { unreachable!(); };
| ^^^^^^^ binding modifier not allowed under `ref mut` default binding mode
|
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
note: matching on a reference type with a non-reference pattern changes the default binding mode
--> src/memory.rs:804:11
|
804 | let MemCell::Array(_, ref mut v) = self.memory.cell_mut(handle) else { unreachable!(); };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&mut _`
help: remove the unnecessary binding modifier
|
804 - let MemCell::Array(_, ref mut v) = self.memory.cell_mut(handle) else { unreachable!(); };
804 + let MemCell::Array(_, v) = self.memory.cell_mut(handle) else { unreachable!(); };
|
error: binding modifiers may only be written when the default binding mode is `move`
--> src/reader.rs:452:71
|
452 | let Some(StackFrame{node:AstNode {content:AstContent::Group(ref mut v),..},..}) = stack.last_mut() else {
| ^^^^^^^ binding modifier not allowed under `ref mut` default binding mode
|
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
note: matching on a reference type with a non-reference pattern changes the default binding mode
--> src/reader.rs:452:20
|
452 | let Some(StackFrame{node:AstNode {content:AstContent::Group(ref mut v),..},..}) = stack.last_mut() else {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&mut _`
help: remove the unnecessary binding modifier
|
452 - let Some(StackFrame{node:AstNode {content:AstContent::Group(ref mut v),..},..}) = stack.last_mut() else {
452 + let Some(StackFrame{node:AstNode {content:AstContent::Group(v),..},..}) = stack.last_mut() else {
|
error: binding modifiers may only be written when the default binding mode is `move`
--> src/reader.rs:646:69
|
646 | let Some(StackFrame{node:AstNode {content:AstContent::Int(ref mut i),..},..}) = stack.last_mut() else { die(); };
| ^^^^^^^ binding modifier not allowed under `ref mut` default binding mode
|
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
note: matching on a reference type with a non-reference pattern changes the default binding mode
--> src/reader.rs:646:20
|
646 | let Some(StackFrame{node:AstNode {content:AstContent::Int(ref mut i),..},..}) = stack.last_mut() else { die(); };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&mut _`
help: remove the unnecessary binding modifier
|
646 - let Some(StackFrame{node:AstNode {content:AstContent::Int(ref mut i),..},..}) = stack.last_mut() else { die(); };
646 + let Some(StackFrame{node:AstNode {content:AstContent::Int(i),..},..}) = stack.last_mut() else { die(); };
|
error: binding modifiers may only be written when the default binding mode is `move`
--> src/reader.rs:661:71
|
661 | let Some(StackFrame{node:AstNode {content:AstContent::String(ref mut s),..},..}) = stack.last_mut() else { die(); };
| ^^^^^^^ binding modifier not allowed under `ref mut` default binding mode
|
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
note: matching on a reference type with a non-reference pattern changes the default binding mode
--> src/reader.rs:661:19
|
661 | let Some(StackFrame{node:AstNode {content:AstContent::String(ref mut s),..},..}) = stack.last_mut() else { die(); };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&mut _`
help: remove the unnecessary binding modifier
|
661 - let Some(StackFrame{node:AstNode {content:AstContent::String(ref mut s),..},..}) = stack.last_mut() else { die(); };
661 + let Some(StackFrame{node:AstNode {content:AstContent::String(s),..},..}) = stack.last_mut() else { die(); };
|
error: binding modifiers may only be written when the default binding mode is `move`
--> src/reader.rs:726:71
|
726 | let Some(StackFrame{node:AstNode {content:AstContent::String(ref mut s),..},..}) = stack.last_mut() else { die(); };
| ^^^^^^^ binding modifier not allowed under `ref mut` default binding mode
|
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
note: matching on a reference type with a non-reference pattern changes the default binding mode
--> src/reader.rs:726:19
|
726 | let Some(StackFrame{node:AstNode {content:AstContent::String(ref mut s),..},..}) = stack.last_mut() else { die(); };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this matches on type `&mut _`
help: remove the unnecessary binding modifier
|
726 - let Some(StackFrame{node:AstNode {content:AstContent::String(ref mut s),..},..}) = stack.last_mut() else { die(); };
726 + let Some(StackFrame{node:AstNode {content:AstContent::String(s),..},..}) = stack.last_mut() else { die(); };
|
error: could not compile `experimental-lisp-0` (lib) due to 9 previous errors
Desired output
Any output which allows me to understand what the error was. Although (see commit 3) I was able to change the code to compile, I still do not understand what it was I did that fixed it. The message "binding modifiers may only be written when the default binding mode is move
" did not guide me, and honestly was even a little misleading.
Two specific recommended changes:
- The errors in the above scroll link the "nightly" version of the edition guide. Because the error is describing a behavior of the stable compiler, and I am running the stable compiler, it should probably instead link the stable version of the same page.
- On some of the errors above, there was a help note saying to add
&mut
in some place or other, and on some of the errors above, there was a help note saying to remove the "unnecessary binding modifier" fromref mut v
. In fact, the solution in every case was to add&mut
, and in no case that I tested did removing theref
/mut
modifiers fromref mut v
produce compiling code— for reference here is the exact diff between commits 2 and 3:
diff --git a/src/memory.rs b/src/memory.rs
index 5654bc9..50d3cfe 100644
--- a/src/memory.rs
+++ b/src/memory.rs
@@ -760,7 +760,7 @@ impl<'a> eval::Eval<'a> {
};
{
// Dump contents of temp into "to" handle
- let &mut MemCell::Array(_, ref mut v) = self.memory.cell_mut($to.clone()) else { unreachable!(); };
+ let MemCell::Array(_, ref mut v) = self.memory.cell_mut($to.clone()) else { unreachable!(); };
v.extend(&temp);
temp.clear(); // Done with temp contents
if $update_to { // Update that the array is cleared until THE VALUE AFTER to_idx
@@ -797,11 +797,11 @@ impl<'a> eval::Eval<'a> {
// We now need to overwrite handle with new_array, but borrow rules make it awkward to see both at once
let mut temp: Vec<MemAddr> = Default::default(); // Don't just clear temp, dealloc its storage
{
- let &mut MemCell::Array(_, ref mut v) = self.memory.cell_mut(new_array) else { unreachable!(); };
+ let MemCell::Array(_, ref mut v) = self.memory.cell_mut(new_array) else { unreachable!(); };
std::mem::swap(&mut temp, v);
}
{
- let &mut MemCell::Array(_, ref mut v) = self.memory.cell_mut(handle) else { unreachable!(); };
+ let MemCell::Array(_, ref mut v) = self.memory.cell_mut(handle) else { unreachable!(); };
std::mem::swap(&mut temp, v);
}
} else {
diff --git a/src/reader.rs b/src/reader.rs
index e6c520d..f7d0ac8 100644
--- a/src/reader.rs
+++ b/src/reader.rs
@@ -449,7 +449,7 @@ pub fn ast<T: std::io::Read>(mut chars: char_reader::CharReader<T>, tag:String,
if is_dot {
if !lisp {
- let Some(&mut StackFrame{node:AstNode {content:AstContent::Group(ref mut v),..},..}) = stack.last_mut() else {
+ let Some(StackFrame{node:AstNode {content:AstContent::Group(ref mut v),..},..}) = stack.last_mut() else {
return Err(Error {at, tag, message:"Found . in a confusing place; must follow a symbol or expression".to_string()});
};
let Some(container) = v.pop() else {
@@ -643,8 +643,7 @@ pub fn ast<T: std::io::Read>(mut chars: char_reader::CharReader<T>, tag:String,
ch)}) // TODO: Sanitize ch printout
}
- let Some(&mut StackFrame{node:AstNode {content:AstContent::Int(ref mut i),..},..}) = stack.last_mut() else { die(); };
-
+ let Some(StackFrame{node:AstNode {content:AstContent::Int(ref mut i),..},..}) = stack.last_mut() else { die(); };
*i = if let Some(i2) = (*i).checked_mul(base_num)
&& let Some(i3) = i2.checked_add(
if !is_neg { digit } else { -digit }
@@ -659,7 +658,7 @@ pub fn ast<T: std::io::Read>(mut chars: char_reader::CharReader<T>, tag:String,
return Err(Error {at, tag, message:format!("Illegal character for identifier: {}", ch)}) // TODO: Sanitize ch printout
}
- let Some(&mut StackFrame{node:AstNode {content:AstContent::String(ref mut s),..},..}) = stack.last_mut() else { die(); };
+ let Some(StackFrame{node:AstNode {content:AstContent::String(ref mut s),..},..}) = stack.last_mut() else { die(); };
s.push(ch);
}
},
@@ -724,7 +723,7 @@ pub fn ast<T: std::io::Read>(mut chars: char_reader::CharReader<T>, tag:String,
}
if let Some(ch2) = append {
- let Some(&mut StackFrame{node:AstNode {content:AstContent::String(ref mut s),..},..}) = stack.last_mut() else { die(); };
+ let Some(StackFrame{node:AstNode {content:AstContent::String(ref mut s),..},..}) = stack.last_mut() else { die(); };
s.push(ch2);
}
},
…for this reason I suggest all the error messages should have suggested adding &mut, not just some of them.
Rationale and extra context
Some bits of context:
- I have never really understood what placing
&
orref
into a pattern-match destructure does (or rather, I know what they do, but I've always been confused which was which). I was already not well equipped to solve this except by trial and error. - The ten sites where the error drops are all doing fundamentally the same thing: Calling a method (such as vec.last_mut) which returns a &mut to a variant type;
let
matching on a particular expected variant type, and extracting a mutable reference to a field within the variant; and then changing the referenced field within the variant.
Various reasons I do not like this error:
- The error seems to make reference to concepts, such as "default binding mode" and "move", which are meaningful to Rust's own compiler authors rather than Rust end users. Rust usually avoids this sin in error messages.
- The error applies to code which built without an error in the 2021 edition. I think this means that Rust has a hightened responsibility to make this newly-minted error message clear.
- I do not feel I understand, reading the error, what "binding modifiers" are. Does it refer to
ref
? tomut
? Both? (The linked "edition" page seems to imply both.) - I do not understand what a "default binding modifier" is, either from the errors or from skimming (it's very dense) the linked "edition" page.
- I do not understand what
move
, inbackticks
, means. I am aware of amove
keyword modifier that can be placed on closures, and I am aware that in Rust when alet
destructure consumes the right side of the=
this is an example of what a compiler textbook would call "move semantics". It seems like a reference to "move" in this context implies we are talking about the compiler textbook definition, but putting it inbackticks
implies we're talking about the Rust keyword somehow, and that doesn't appear near the error site. - Most critically, the error seems in fact to point me away from the problem, rather than toward it. To wit, the message implies the problem is with my binding modifiers, whereas in fact the problem was that somewhere else I had matched the pattern in a (newly as of edition 2024?) incorrect way, which had the symptom of causing the
ref mut
to be incorrect for what Rust now concludes my left-side pattern to express. I don't know if this (the true root of my error) is actually detectable, but some things that would have helped me work it out anyway are:- The compiler giving me enough information to understand why it concluded this particular variable binding was ineligible for
ref mut
— "because this match expression is garble farble, this variable binding must use move semantics". - Talking through what I think is happening here: In this case, the right side of the expression returns a
&mut
, so I would have expected the left side of the expression was matching a&mut
. But maybe in Edition 2024, matching a "written as if move type" destructure on the left with a &mut on the right causes Rust to automatically dereference the &mut. This would explain why Rust is making confusing references to "move
", and why adding&mut
(suppressing the auto-dereference) fixed it. If this is the true problem: Even if Rust doesn't know where my bug originated, Rust could detect whether suppressing the (surprising, and not present in older Rusts) auto-dereference would have made my illegalref mut
legal, and when suggesting adding&mut
used more explicit verbiage (e.g. maybe a whole sentence like "Because this is a value rather than a reference match, the binding modifier(s) are not allowed:ref
,mut
"— I don't know what vocabulary is correct here, although I do know I dislike the word "implied" in the current text "make the implied reference pattern explicit" because apparently a reference is not implied in edition 2024, or else myref mut
would have been legal.) - Not recommending removing the
ref mut
, as they are in fact needed (removing them, again, seems to produce nonworking code).
- The compiler giving me enough information to understand why it concluded this particular variable binding was ineligible for
- One of the ten error sites underlines "ref mut" and says: "binding modifier not allowed under
ref mut
default binding mode". This sentence is the most confusing of all, as it seems to imply you are not allowed to useref mut
when you are usingref mut
. That feels circular, like putting up a sign that says "signs prohibited".
Other cases
Rust Version
rustc 1.88.0 (6b00bc388 2025-06-23)
binary: rustc
commit-hash: 6b00bc3880198600130e1cf62b8f8a93494488cc
commit-date: 2025-06-23
host: x86_64-unknown-linux-gnu
release: 1.88.0
LLVM version: 20.1.5
Anything else?
For comparison, I tried cargo +nightly build --release
with rustc 1.90.0-nightly (a84ab0c 2025-07-06) (x86_64-unknown-linux-gnu LLVM version: 20.1.7) and got what seems, eyeballing it, to be identical output.
The system on which I am running these tests is running Debian Unstable (trixie) but I do not believe this will impact anything.
Notes on my three commits up top: On line 646 of reader.rs
, there is a difference between "commit 1" and commits 2 and 3. This change is irrelevant to the issues above and can be ignored (I added a && let
on this line and this is why I switched from edition 2021 to 2024 in the first place). The reason I believe my &mut
changes in commit 3 are the correct ones (despite still not totally understanding them) is because I have extensive regression tests (run ./develop/regression.py -a
from root of any commit), so I strongly believe commit 3 is functionally identical to commit 1.