gleam-lang/gleam · error
InMemoryFile::into_content called with multiple references
Error message
InMemoryFile::into_content called with multiple references
What it means
InMemoryFile::into_content (memory.rs:402) unwraps the file's Rc<RefCell<Vec<u8>>> buffer with Rc::try_unwrap(...).expect("InMemoryFile::into_content called with multiple references"). The buffer Rc gains extra owners whenever a reader/writer handle to that in-memory file is cloned and still alive, so into_content enforces sole-ownership exactly like InMemoryFileSystem::into_contents (error 34), but per file.
Source
Thrown at compiler-core/src/io/memory.rs:402
..Self::default()
}
}
/// Checks whether this is a directory's entry.
pub fn is_directory(&self) -> bool {
matches!(self.node, InMemoryFileNode::Directory)
}
/// Returns this file's contents if this is not a directory.
///
/// # Panics
///
/// Panics if this is not the only reference to the underlying files.
///
pub fn into_content(self) -> Option<Content> {
let buffer = self.node.into_file_buffer()?;
let contents = Rc::try_unwrap(buffer)
.expect("InMemoryFile::into_content called with multiple references")
.into_inner();
// All null bytes are usually from when a binary file is empty, and
// aren't particularly useful as text, so we treat them as binary.
if contents.iter().all(|byte| *byte == 0) {
return Some(Content::Binary(contents));
}
match String::from_utf8(contents) {
Ok(s) => Some(Content::Text(s)),
Err(e) => Some(Content::Binary(e.into_bytes())),
}
}
}
impl Default for InMemoryFile {
fn default() -> Self {
Self {View on GitHub (pinned to 7e623aa83d)
Solutions
- Drop all open handles for the path before converting: `drop(reader); drop(writer);` then call into_contents().
- Prefer read_bytes()/files() for inspection so no ownership conversion is needed.
- Scope handles tightly (inside a block or function) so they die before the into_contents call site.
- Maintainer option: make into_content return Result<Option<Content>> or expose strong_count for pre-checking.
Example fix
// before: handle still alive during conversion
let mut w = fs.writer(&path);
w.write(&path, "data")?;
let files = fs.into_contents(); // panics: buffer Rc shared with w
// after: release handles first
{
let mut w = fs.writer(&path);
w.write(&path, "data")?;
} // handle dropped here
let files = fs.into_contents(); Defensive patterns
Strategy: validation
Validate before calling
// Drop all open handles for in-memory paths before the final conversion
{
let mut w = fs.writer(&path);
w.write(&path, &contents)?;
} // writer (and any readers) dropped here — buffer Rc count back to 1
let files = fs.into_contents(); Prevention
- Scope every reader/writer handle tightly so it cannot outlive into_contents().
- Don't store io handles in long-lived structs when you plan to consume the filesystem afterwards.
- Use read_bytes()/files() for inspection to avoid ownership conversion entirely.
When it happens
Trigger: Opening the same in-memory path multiple times (each open_read/open_write returns handles sharing the buffer), keeping one handle alive in a variable or struct, and then calling into_contents()/into_content() on the filesystem/file while the handle is still referenced.
Common situations: Tests that keep an io::Handle around for assertions after the compiler wrote files, or wrapper FileSystems (e.g. wasm_filesystem.rs delegating to the imfs) that retain handles past the final into_contents call.
Related errors
- InMemoryFileSystem::into_files called on a clone
- channel buffer write
- JavaScript generator could not identify imported module name
- Custom type must have at least one definition here
- `panic` expression evaluated.
AI-assisted analysis of gleam-lang/gleam@7e623aa83d (2026-08-17).
Data as JSON: /api/errors/11c7be67cd0d1fd5.
Report an issue: GitHub.