Skip to content
Select theme

How to copy or move a file

Short answer: fs::copy("from", "to") copies a file and leaves the original where it was. fs::rename("from", "to") moves it — the original name is gone afterwards. Both take a from-path and a to-path and return io::Result. The one trap: fs::rename refuses to cross a filesystem boundary, so a move onto another drive is copy-then-remove.

From a fresh project — cargo new copy-move-demo, then replace the contents of src/main.rs with each block below and run it with cargo run. Each block creates the files it works on, so it runs on its own and in any order. Paths are relative to the directory you run from, which under cargo run is the project root.

15 bytes copied
original untouched: "meeting at ten\n"
target replaced: "meeting at ten\n"

The return value is the number of bytes copied, which you rarely need except for a progress line. Three behaviours are worth knowing:

  • An existing target is overwritten, not merged or rejected — the old backup contents above are gone after the copy.
  • The source’s permission bits come along: a file with mode 755 produces a copy with mode 755, even when it replaces an existing target.
  • Both paths must be files. fs::copy on a directory returns an error rather than recursing.
old name still there: false
new name has it: "first draft\n"

Nothing is read or rewritten: the rename is one directory-entry update, which is why it is instant on a 10 GB file and why the atomic-write page uses it as its commit point. An existing target is replaced in the same operation. Directories rename the same way as files, with one condition the next section is about.

A rename does not move bytes — it re-points a directory entry, and a directory entry cannot reach into another filesystem. The standard library documents that fs::rename fails when the two paths are on different filesystems. On this machine, renaming a file from the startup volume to an external USB drive returned exactly:

Cross-device link (os error 18)

Its io::ErrorKind is CrossesDevices, and the error kind is the thing to match on — the message wording varies by platform, the kind does not. When a move has to cross that line, fall back to copy and remove:

original removed: true
arrived: "final numbers\n"

Within one filesystem the rename path runs and the fallback never fires, so the move stays atomic. Across the boundary it degrades to copy-then-remove, which is not atomic — a crash between the two steps leaves two copies. That is the price of crossing volumes; fs::copy itself has no such restriction, since it reads and writes bytes rather than re-pointing entries.