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

  1. Drop all open handles for the path before converting: `drop(reader); drop(writer);` then call into_contents().
  2. Prefer read_bytes()/files() for inspection so no ownership conversion is needed.
  3. Scope handles tightly (inside a block or function) so they die before the into_contents call site.
  4. 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

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


AI-assisted analysis of gleam-lang/gleam@7e623aa83d (2026-08-17). Data as JSON: /api/errors/11c7be67cd0d1fd5. Report an issue: GitHub.