gleam-lang/gleam · error

InMemoryFileSystem::into_files called on a clone

Error message

InMemoryFileSystem::into_files called on a clone

What it means

InMemoryFileSystem keeps its files in Rc<RefCell<HashMap<...>>>; into_contents consumes self and calls Rc::try_unwrap(...).expect("InMemoryFileSystem::into_files called on a clone") to take sole ownership (memory.rs:63). Because cloning the filesystem is a cheap Rc clone, any clone still alive anywhere (a test variable, a struct field, a dropped-later scope) makes try_unwrap fail and this panic fire. The # Panics doc on the function states exactly this contract.

Source

Thrown at compiler-core/src/io/memory.rs:63

impl InMemoryFileSystem {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn reset(&self) {
        self.files.deref().borrow_mut().clear();
    }

    /// Returns the contents of each file, excluding directories.
    ///
    /// # Panics
    ///
    /// Panics if this is not the only reference to the underlying files.
    ///
    pub fn into_contents(self) -> HashMap<Utf8PathBuf, Content> {
        Rc::try_unwrap(self.files)
            .expect("InMemoryFileSystem::into_files called on a clone")
            .into_inner()
            .into_iter()
            .filter_map(|(path, file)| file.into_content().map(|content| (path, content)))
            .collect()
    }

    /// All files currently in the filesystem (directories are not included).
    pub fn files(&self) -> Vec<Utf8PathBuf> {
        self.files
            .borrow()
            .iter()
            .filter(|(_, f)| !f.is_directory())
            .map(|(path, _)| path)
            .cloned()
            .collect()
    }

    #[cfg(test)]

View on GitHub (pinned to 7e623aa83d)

Solutions

  1. Drop every clone before converting: `drop(fs_clone);` or structure the test so clones go out of scope before `fs.into_contents()`.
  2. Don't clone at all — use the non-consuming `fs.files()` / `fs.read_bytes()` for inspection and save into_contents for the final ownership handoff.
  3. If a wrapper type holds a clone, redesign it to borrow (&InMemoryFileSystem) instead of owning a clone.
  4. For maintainers: consider returning Result or exposing a strong_count check instead of panicking.

Example fix

// before: clone alive during conversion
let fs = InMemoryFileSystem::new();
let snapshot = fs.clone();
// ...compile with fs...
let files = fs.into_contents(); // panics: Rc::try_unwrap fails

// after: drop the clone first, or don't clone
let fs = InMemoryFileSystem::new();
// ...compile with fs...
let names = fs.files();      // inspect without consuming
drop(snapshot);              // if you must clone, drop it first
let files = fs.into_contents();
Defensive patterns

Strategy: validation

Validate before calling

// Enforce the single-owner contract before converting (library authors with
// access to the internals):
fn into_contents_if_sole(fs: InMemoryFileSystem) -> Result<HashMap<Utf8PathBuf, Content>, InMemoryFileSystem> {
    // cheap structural check: clone discipline is on you; track clones manually
    // e.g. wrap InMemoryFileSystem and count clones handed out, require count == 0 here
}

// Practical caller-side validation: audit that no clone exists
drop(snapshot_writer); // every variable/field holding fs.clone()
let files = fs.into_contents();

Prevention

When it happens

Trigger: Any code that does `let fs2 = fs.clone()` (tests, wrappers, the test-package-compiler/test-project-compiler harnesses) and then calls fs.into_contents() while fs2 is still in scope or moved into another object. Also a clone captured by a closure or held in a Reader/Writer wrapper.

Common situations: Writing unit tests against gleam_core with the in-memory IO: you clone the filesystem to hand to the compiler and keep another copy for assertions, then call into_contents for golden-file comparison (as native_file_copier/tests.rs does dozens of times).

Related errors


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