# Designing a Paths Julep

**URL:** <https://discourse.julialang.org/t/designing-a-paths-julep/124335>\
**Category:** Internals & Design\
**Tags:** proposal, filesystem, path, rfc\
**Created:** [January 1, 2025, 12:37pm UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335 "2025-01-01T12:37:25Z")\
**Posts on this page:** 20\
**Page:** 8

<div class="post-metadata">

**Author:** ![kkretschmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kkretschmer/32/7474_2.png) [@kkretschmer](https://discourse.julialang.org/u/kkretschmer)\
**Post date:** [March 19, 2025, 8:54am UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/142 "2025-03-19T08:54:29Z")

</div>

Recently there was an [LWN news item](https://lwn.net/Articles/1013876/) pointing to an article on the Go blog about [Traversal-resistant file APIs](https://go.dev/blog/osroot).

---

<div class="post-metadata">

**Author:** ![savq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/savq/32/22063_2.png) [@savq](https://discourse.julialang.org/u/savq)\
**Post date:** [March 19, 2025, 10:07pm UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/143 "2025-03-19T22:07:15Z")

</div>

That’s a nice API. I’d also be nicer in Julia since we could use a `do` block instead of `defer`.

---

<div class="post-metadata">

**Author:** ![sairus7](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sairus7/32/10816_2.png) [@sairus7](https://discourse.julialang.org/u/sairus7)\
**Post date:** [March 26, 2025, 5:19pm UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/144 "2025-03-26T17:19:49Z")

</div>

My two cents here: if Paths API has some kind of abstract interface, then maybe it can be used consistently across packages like Tar.jl, ZipArchives.jl, ZipFile.jl (maybe with some constraints)?

---

<div class="post-metadata">

**Author:** ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)\
**Post date:** [April 7, 2025, 8:05am UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/145 "2025-04-07T08:05:01Z")

</div>

> [@kkretschmer](#):
>
> Recently there was an [LWN news item](https://lwn.net/Articles/1013876/) pointing to an article on the Go blog about [Traversal-resistant file APIs](https://go.dev/blog/osroot).

Thanks for the link! Incidentally this touches on one of the tensions in the current design: my desire to break with select conventions in the name of consistency/safety.

For instance, currently `p"/some/absolute/path" * p"../../../../../../../../etc/passwd"` will raise an error once we try to get the parent directory of `p"/"`, however the “standard” behavior is for the parent of `/` to be `/` 😕.

I particularly enjoyed reading this section:

> If the attacker controls part of the local filesystem, they may be able to use symbolic links to cause a program to access the wrong file:
> 
> ```julia
> // Attacker links /home/user/.config to /home/otheruser/.config:
> err := os.WriteFile("/home/user/.config/foo", config, 0o666)
> 
> ```
> 
> If the program defends against symlink traversal by first verifying that the intended file does not contain any symlinks, it may still be vulnerable to [time-of-check/time-of-use (TOCTOU) races](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use), where the attacker creates a symlink after the program’s check:

Since this is exactly the issue that is systematically eliminated by requiring the use of a FD-as-Path type, as I am now experimenting with (and based on encouraging results, advocating for).

I don’t think it would be too hard to extend the proposal slightly and have rooted-ness be an extra flag that the FD-as-Path type can have, which is then automatically propagated to all path joining operations.

From reading that article, with this proposal will give us a better story with this class of vulnerabilities than Go 🙂

> [@savq](#):
>
> That’s a nice API. I’d also be nicer in Julia since we could use a `do` block instead of `defer`.

RAII would be nice here, but in the current experiment I’m just using a finaliser. I’m not sure that `do` is really what we want, since it’s good if the same handle is passed around more instead of being dropped and re-acquired.

---

<div class="post-metadata">

**Author:** ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)\
**Post date:** [April 7, 2025, 8:06am UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/146 "2025-04-07T08:06:03Z")

</div>

> [@sairus7](#):
>
> My two cents here: if Paths API has some kind of abstract interface, then maybe it can be used consistently across packages like [Tar.jl](https://juliaregistries.github.io/General/packages/redirect_to_repo/Tar), [ZipArchives.jl](https://juliaregistries.github.io/General/packages/redirect_to_repo/ZipArchives), [ZipFile.jl](https://juliaregistries.github.io/General/packages/redirect_to_repo/ZipFile) (maybe with some constraints)?

Sure, why not 😃 How does [julia-basic-paths/abstractpaths.jl at main - tec/julia-basic-paths - Code by TEC](https://code.tecosaur.net/tec/julia-basic-paths/src/branch/main/abstractpaths.jl) look to you?

---

<div class="post-metadata">

**Author:** ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)\
**Post date:** [April 7, 2025, 8:15am UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/147 "2025-04-07T08:15:46Z")

</div>

> [@tecosaur](#):
>
> It’s incomplete, and among other things the naming has to be worked out (do we really want to call a handle to a resource a `Path`? I’m not sure but `FilesystemEntityHandle` is way too awkward and I haven’t had any naming brainwaves).

It’s slightly imprecise, but I’m thinking that maybe `PathHandle` is the name to go with here. I think having “handle” in the name is important, and while `FSHandle` etc. are most accurate, by putting `Path` in the name we get an explicit connection to the `Path` type and an indication of the relationship between the two. Appearing as a completion with `Path<tab>` is a nice bonus. I think together this more than makes up for the loss in accuracy.

So, with this, there are three kinds of system-native paths:

- `Path` as a “pure” path for the current system (an alias of `PosixPath` or `WindowsPath`)
- `PathHandle` for a file descriptor/filesystem handle produced from a `Path`
- `DirEntry` is a parent directory `PathHandle` combined with a relative `Path` and (optional) metadata.

All of these types can be converted between (e.g. `PathHandle` to a `Path`, `DirEntry` to a `PathHandle`, etc.) using the platform filesystem APIs. We can do this automatically where convenient, and require a particular type when we want to prod the user into behaving more safely (e.g. requiring them to acquire a handle, to encourage them to re-use it).

---

<div class="post-metadata">

**Author:** ![goerz](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerz/32/3269_2.png) [@goerz](https://discourse.julialang.org/u/goerz)\
**Post date:** [August 30, 2025, 12:43pm UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/148 "2025-08-30T12:43:49Z")

</div>

Since @tecosaur explicitly requested further commenting in

> [@The strangeness (or not) of \* as string concatenation](https://discourse.julialang.org/t/the-strangeness-or-not-of-as-string-concatenation/131943/57):
>
> If I could direct you over to that thread, I’d love to hear more. At this stage, I think I can say that thread will eventually become a something, and I’d like that something to be of high enough quality to be worth serious consideration for inclusion in Julia proper (which is somewhat needed for the value of a Path type to be realised, given open, read, etc.). There are small pile of design compromises that need to be made. More shared thoughts on what the right priorities, trade-offs, and ot…

I already expressed most of my views during the original discussion, in [Designing a Paths Julep - #58 by goerz](https://discourse.julialang.org/t/designing-a-paths-julep/124335/58), but in the context of the point I was trying to make in the context of the [“The strangeness (or not) of \* as string concatenation” thread](https://discourse.julialang.org/t/the-strangeness-or-not-of-as-string-concatenation/131943), the worry I took away from the discussion here is pretty much summed up with

> [@adienes](#):
>
> the counterpoint being I realllllly think it would be a shame if Julia chose once again to be a special flower and reject overwhelmingly consistent & near universal precedent for the sake of pseudo-mathematical ideological “purity”

One aspect of such “purity” is @jar1’s (and others’) stance

> [@jar1](#):
>
> Most simply: **`Base./` is defined to be division. I don’t want to use it for something unrelated.**

It’s not that I don’t understand this at some level, and it’s hard to argue about it completely objectively. But the experience of every other programming language that has `Path` objects has shown that there is no practical _problem_ with using `/` for something completely unrelated to division, in the context of paths. These functionalities do not clash, in practice.

But beyond the purity of “division”:

> [@The strangeness (or not) of \* as string concatenation](https://discourse.julialang.org/t/the-strangeness-or-not-of-as-string-concatenation/131943/66):
>
> If the argument is “`*` is the only appropriate operator for string concatenation, because of [abstract string algebra in theoretical computer science](https://en.wikipedia.org/wiki/String_operations)”, then I’m starting to have serious problems. Because then we’re in the territory of “I need to understand abstract algebra concepts to work with Julia / Julia is only for PhDs”, which is not a good place to be in.
> 
> And if you then go further and say "because we have `*`, abstract algebra demands that we also have `/` (despite there not being a multiplicative inverse for strings), then I think we’ve totally lost the plot, and we end up with "Oh, then we must also have the same algebra with `*` and `/` for `Path` objects, and then `p"folder/sub" / p"sub"` ends up meaning the exact opposite of what it means in literally every other programming language that has `Path` objects.

I’d also want to strongly re-emphasize

> [@DNF](#):
>
> But since a path is a kind of string (or closely related), it’s very unfortunate to use the string concatenation operator for something that’s very different from concatenation. Especially since concatenation is something you probably want to do.
> 
> IMO, `*` is the worst possible operator for path join, better to use something completely different.

But enough about the `joinpath` operator 😉

The other point @tecosaur was bringing up was

> [@The strangeness (or not) of \* as string concatenation](https://discourse.julialang.org/t/the-strangeness-or-not-of-as-string-concatenation/131943/57):
>
> > [@The strangeness (or not) of \* as string concatenation](https://discourse.julialang.org/t/the-strangeness-or-not-of-as-string-concatenation/131943/50):
> >
> > In Python’s `pathlib`, `Path("user_content/" + untrusted_file)` is simply `PosixPath('user_content/../../../../../../etc/passwd')`, which seems perfectly alright.
> 
> I’d describe this as perfectly _dangerous_ not alright. There’s a pile of CVEs across all sorts of software and libraries from exactly this behavior.

It’s dangerous because there’s untrusted input being used. People have to be aware of untrusted input, and sanitize it before letting it flow through the rest of their program. But that’s not _your_ responsibility. Or rather, you’re in no position to “fix” this in a path library, and I’d be concerned that any well-meaning attempt to fix it is only going to mess up perfectly legitimate use cases. Stick to the solutions that have been found to work in other ecosystems, such as Python’s pathlib.

A `Path('user_content/../../../../../../etc/passwd')` is absolutely something I might want to do, and yes, it should `resolve` to `/etc/passwd`. The way to sanitize `Path("user_content/" + untrusted_file)` is to `resolve` it and check that the result is “secure”. Maybe “secure” means it’s in a subfolder of `user_content`, or maybe that it’s in a subfolder of the current user’s home directory. The point is, you can’t know, so how could you possibly “fix” this? The CVEs are for libraries improperly using untrusted input, not for the `Path` library. the `Path` library isn’t the problem here, and it’s not the place to fix anything.

I would be _strongly_ advise against changing the behavior of existing solutions for things like `relative_path / absolute_path -> absolute_path`. Even the existing string-based `joinpath` in Julia implements that behavior. Other pathlib implementation adopt this for good reason. Yes, it’s potentially dangerous with unsanitized input, but it’s important for common practical use cases, such as `join_path(cwd(), location)` not having to distinguish whether `location` is relative or absolute.

Basically,

> [@davidanthoff](#):
>
> I think I would try to keep this very simple

As for some of the “ideas” for design goals floating around,

> [@jules](#):
>
> - rejecting invalid path segments at creation
> - disallowing a root segment within a path’s segments
> - disallowing joining a path with an absolute one
> - clear handling of special `.` and `..` segments

No! Don’t do any of these things! Don’t fix it if it ain’t broken. _All_ we need is a `Path` object that understands the “segments” that make up a file path, and functions like `parent`, `name`, `suffix`, `stem`, `relative_to`, etc. to manipulate them.

Don’t get in the way of constructing “weird” paths, like “rejecting invalid path segments”, or applying any kind of normalization prematurely.

Then _on top_ of that, you can normalize paths to deal with “handling of special `.` and `..` segments”. At that point, you still don’t want to access the actual file system: it should be possible to generate normalized Unix paths on a Windows system and vice versa.

Then lastly, you resolve paths on the actual file system, and that’s where things like symlinks come into play. (I’m not sure whether the name `resolve` is appropriate at the normalization stage or at the resolve-on-filesystem stage; again: look at the terminology used in existing implementations). At that level, _maybe_ you also want to add some functionality to verify that certain paths are “secure”, but I’d be pretty careful with that.

---

<div class="post-metadata">

**Author:** ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)\
**Post date:** [October 15, 2025, 10:27am UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/149 "2025-10-15T10:27:35Z")

</div>

> [@goerz](#):
>
> Since @tecosaur explicitly requested further commenting in

Thanks Michael. It’s been a while, but I appreciate you following through on this.

What exactly a good `Path` type looks like is a much trickier question than I first thought, and I hearing other people’s hopes/priorities/concerns is hugely helpful.

> [@goerz](#):
>
> But enough about the `joinpath` operator 😉

I’m sorry, but I’m not going to touch the `*` & `/` discussion at this time 🙈. I have opinions, but I’m concerned that the bikeshedding around that has crowded out some of the more critical capability/behavior discussion. I still plan on leaving infix operators out of the proposal.

> [@goerz](#):
>
> I’d be concerned that any well-meaning attempt to fix it is only going to mess up perfectly legitimate use cases.

I am too, but I want to be as as much as is _reasonably_ to encourage safety. To this end, concrete examples of how some of the ideas I’m throwing around could backfire would be very helpful.

**Note:** Sometimes the difference in backgrounds/interpretation makes it hard for me to build the same mental model from an abstract description of a problem as the same concept/thought that I imagine existed in your or other commenter’s heads. It also makes it harder to identify when there’s a misunderstanding/miscommunication. I’m rather convinced that this is happening in the thread because I’ve read things that seem “silly” to me from people who I’m quite sure aren’t having silly thoughts.

Your `joinpath(cwd(), location)` example is exactly the sort of example that’s helpful in this way, though elaboration on where you tend to see/think of `location`coming from wouldn’t hurt 🙂

Hmm, building a checklist of “things that should still ‘just work’” could be a good idea…

…anyway, back to your reply:

> [@goerz](#):
>
> The point is, you can’t know, so how could you possibly “fix” this?

Magically knowing what is/isn’t “secure” / “good” is indeed beyond what a path library can do. However, what we can do is require a you to be _explicit_ about what behavior you expect/permit.

For instance, say you have

```julia-auto
pathfragment::String = some_source()

```

if you expect it is going to be a file name with no directory component, you can do:

```julia-auto
p"some/parent/$pathfragment.txt"

```

and get an error when `pathfragment` contains directories or invalid characters (like a null byte). The same applies when you want a directory and you do:

```julia-auto
p"some/parent/$pathfragment"

```

This logic applies because you are interpolating a `String`.

Say you really do want to allow `pathfragment` to have multiple segments. Interpret the `pathfragment` as a `Path` and the single-segment expectation no longer applies:

```julia-auto
pathfragment = parse(Path, some_source())
p"some/parent/$pathfragment"

```

You aren’t prevented from using untrusted input, you just need to be explicit about what you expect `pathfragment` to be.

> [@goerz](#):
>
> Don’t get in the way of constructing “weird” paths, like “rejecting invalid path segments”

Examples of why it’s a bad idea to, for example, prevent paths from being constructed that contain null bytes would be helpful.

> [@goerz](#):
>
> At that point, you still don’t want to access the actual file system: it should be possible to generate normalized Unix paths on a Windows system and vice versa.

I’m currently thinking we want to three levels of path-related objects:

- Strings (parse to a path, and as path segments)
- Paths (as a series of segments, disconnected from the filesystem)
- Filesystem resources (i.e. file handles)

Drawing the right lines between what each kind of object is responsible for takes quite a bit of thought (for me at least). For example, I’m currently rather inclined to think it should only be possible to do filesystem operations (read/write/move/etc.) on filesystem resources, _not_ paths directly — just make it easy to get a resource handle from a path. This is specifically motivated by TOCTTOU-type issues.

---

<div class="post-metadata">

**Author:** ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)\
**Post date:** [November 7, 2025, 4:39pm UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/150 "2025-11-07T16:39:23Z")

</div>

While I continue to wish for more time than I have, it’s worth mentioning a likely expansion to the scope of this work: VFS support.

Instead of having

```julia-auto
struct PosixPath <: SystemPath
    path::GenericPlainPath{PosixPath}
end

```

I’m thinking we could have

```julia-auto
struct PosixPath{FS <: AbstractFilesystem} <: SystemPath{FS}
    fs::FS
    path::GenericPlainPath{PosixPath}
end

```

As a type parameter, this should be “free” for singleton types like

```julia
struct LocalFilesystem <: AbstractFilesystem end

const localfs = LocalFilesystem()

```

It would allow for other VFS types to be defined like `Tarball`, `Zip`, `S3`, etc. though. Operations like `readdir` and `dirname` would then produce path types that are specific to the same filesystem, allowing for generic code that “just works” with a tarball as easily as a folder on your local filesystem (with the appropriate `open` methods etc.).

The value of an approach like this can be seen in across a number of languages and libraries, but one example I want to point out is [GitHub - spf13/afero: The Universal Filesystem Abstraction for Go](https://github.com/spf13/afero).

This goes beyond implementations like [GitHub - platformdotnet/Platform.VirtualFileSystem](https://github.com/platformdotnet/Platform.VirtualFileSystem), which operate with special URI prefixes (e.g. `zip://`), with support for composite “filesystems” like `CopyOnWriteFs`, `CacheOnReadFs`, and `BasePathFs` (a jailed FS).

This immediately got me thinking about Julia’s specialisation/composability model, which seems like a natural fit for such a design (e.g. wrap a compressed `Tarball` FS in a `CacheOnReadFS`). Afero also does a great job providing examples of practical uses for a system.

Perhaps we could even introduce a sysimage FS that allows for the depot to be folded into a single file which packages could “just work” with by changing `@ __DIR__ `\* to be a `SysimageFS` instead of `LocalFS`…

This is a bit “pie in the sky” at the moment, but I’m excited by the apparent potential of a path type that really makes the most of Julia’s strengths, and VFS support seems like it could fit really nicely into the design.

I’m looking forward to when I next get a slice of time to dedicate to working on the Paths Juliap + prototype implementation!

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [November 7, 2025, 5:57pm UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/151 "2025-11-07T17:57:03Z")

</div>

> [@tecosaur](#):
>
> allowing for generic code that “just works” with a tarball as easily as a folder on your local filesystem

This can definitely be made cleaner/even more generic, but note that it already works in Julia!  
Eg see above in this thread:

> [@Designing a Paths Julep](https://discourse.julialang.org/t/designing-a-paths-julep/124335/94):
>
> More generally, I skimmed through this thread but still not sure I understand the scope. Is this julep expected to accomodate stuff like remote paths? Current Julia filesystem API makes it kinda-possible to define the Base FS interface for remotes, so that users can write functions that work with either local or remote files. But it definitely requires some care. One example is my package to work with [OSF.io](http://OSF.io): [GitHub - JuliaAPlavin/OpenScienceFramework.jl](https://github.com/JuliaAPlavin/OpenScienceFramework.jl). Would be great not to lose this abili…

---

<div class="post-metadata">

**Author:** ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)\
**Post date:** [November 7, 2025, 6:07pm UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/152 "2025-11-07T18:07:05Z")

</div>

> [@aplavin](#):
>
> but note that it already works in Julia!

Indeed, you can implement `open` etc. for a custom path type currently, but because of the nature of Julia and its ecosystem it’s currently a bit of a NxM problem (in practice, if not in theory).

Code that deals with paths in other packages needs to either extend your OSF.jl, or you need to write extensions for them.

I think we have a lot to gain from a unifying approach that allows you to do `struct OSFRecord <: AbstractFilesystem` and end up with a path type that just works across the ecosystem.

It goes without saying that even if this goes as planned, it will take time to spread. The best time to do this was 2014, the second best time is now.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [November 7, 2025, 8:27pm UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/153 "2025-11-07T20:27:35Z")

</div>

> [@tecosaur](#):
>
> because of the nature of Julia and its ecosystem it’s currently a bit of a NxM problem (in practice, if not in theory).

It hasn’t been _that_ bad in my experience. I have a few more “virtual path” types – see eg SquashFS.jl. For a lot of code to work with these paths in practice, only relatively few functions need to be supported.  
There are definitely some places in the Julia path handling that make it painful to plug a custom function inside, but they are relatively rare.

---

<div class="post-metadata">

**Author:** ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)\
**Post date:** [December 7, 2025, 7:26am UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/154 "2025-12-07T07:26:13Z")

</div>

> [@aplavin](#):
>
> For a lot of code to work with these paths in practice, only relatively few functions need to be supported.

Sure, but then some library writes a function that expects a path to be provided as a `::String`/`::AbstractString`, and is no longer matters that you’ve implemented `open(::MyVirtualPath)` because of the incompatible layers inbetween.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [December 7, 2025, 1:09pm UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/155 "2025-12-07T13:09:17Z")

</div>

Indeed, that’s true!  
In general, there’s no way around it: say if a package wraps a C library, it can fundamentally only accept regular strings – not arbitrary Julia objects. If the package is pure Julia otoh, it can relax the type constraint on the argument. And that’s generally the advice in Julia anyway, not just for paths: eg, there are functions out there that accept `Vector` while they can take `AbstractVector` or just an arbitrary iterable.

---

<div class="post-metadata">

**Author:** ![asinghvi17](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/asinghvi17/32/8272_2.png) [@asinghvi17](https://discourse.julialang.org/u/asinghvi17)\
**Post date:** [December 7, 2025, 1:29pm UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/156 "2025-12-07T13:29:10Z")

</div>

I guess the shortcut solution to that would be some `materialize(::AbstractPath)::String` function that puts it on the filesystem? At least for C libraries that require paths as strings, and similar.

You could also do multiple dispatch for libraries like GDAL (which have their own ways of handling web and S3 files, for example).

But the real solution would probably just be a lot of working through the ecosystem. I can see `materialize` being useful in general too, since you might want a fast local copy of the file for many uses…

---

<div class="post-metadata">

**Author:** ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)\
**Post date:** [December 7, 2025, 4:34pm UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/157 "2025-12-07T16:34:46Z")

</div>

> [@aplavin](#):
>
> only accept regular strings – not arbitrary Julia objects

Right. But if we have an `AbstractPath` type (with an interface), then (going forwards) we _can_ have libraries that accept all sorts of path types without making arguments untyped 🙂

> [@asinghvi17](#):
>
> I guess the shortcut solution to that would be some `materialize(::AbstractPath)::String` function that puts it on the filesystem?

Or just `string(::AbstractPath)`

> [@aplavin](#):
>
> In general, there’s no way around it: say if a package wraps a C library, it can fundamentally only accept regular strings – not arbitrary Julia objects.

`Base.cconvert` should let you `ccall` with a path directly, no problem.

> [@asinghvi17](#):
>
> You could also do multiple dispatch for libraries like GDAL (which have their own ways of handling web and S3 files, for example).

Indeed.

> [@asinghvi17](#):
>
> But the real solution would probably just be a lot of working through the ecosystem.

Introducing an `AbstractPath` type that’s taken seriously is going to require this regardless. What we can do though, is try our best to make it _worth it_.

---

<div class="post-metadata">

**Author:** ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)\
**Post date:** [January 12, 2026, 8:12am UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/158 "2026-01-12T08:12:00Z")

</div>

It’s becoming clear to me that:

1. It’s very useful to separate out the concepts of a filesystem _path_ and a real resource on the filesystem (you see this split in many langs, e.g. Python’s pure and concrete paths)
2. While it’s _possible_ to have filesystem resource handles as a path subtype (i.e. I can implement all of the currently required methods), it seems a bit awkward (you really need to squint for it to be Liskov-substitutable)

What I’m now contemplating is having an entirely separate `AbstractHandle` type (with `{FilesystemHandle, DirEntry} <: PathHandle <: AbstractHandle`), which can be produced from `handle(::Path)`.

This makes the separation completely clean (which I very much like), but there are a few aspects of this that I have misgivings around:

1. Does this encourage people to write good generic code as well? Ideally I think we want to have path handles be _the_ primary way of moving around the filesystem (we can descend through handles, copy, move, rename, change directory, and more all purely with handles — there’s been a concerted interest in adding more handle-based operations to operating systems over the past two decades). Functions that take a `::Path` argument and then internally just call `dothing(handle(::Path), ...)` whenever that argument is used are fundamentally more fragile (e.g. susceptible to TOCTTOU) than handle-based code.  
I’m very much concerned with how we can design this so that the easiest way to use paths results in code that’s the most robust/follows best practices.  
Does the path/handle split (without a common supertype) help or hinder?
2. Any “easy” half-solution to (1) could be to just avoid implementing any filesystem-interacting methods for `Path` (i.e. force people to get a handle, and so help reinforce the association that “Path is for abstract work, handles are for when you need to interact with the filesystem”). This seems like it could be annoying, but alleviated through good error messages which suggest what the use likely wants to use.
3. Is it viable for Base to define + export `handle`? I think it’s a good fit for a Base function since I think there’s a general concept of a “resource handle” that we can clearly articulate, and a good generic API (`handle(::Resource, params...) -> ResourceHandle <: AbstractHandle`). Unsurprisingly, it seems like this method is [quite common](https://juliahub.com/ui/Search?type=symbols&q=handle&u=define&t=function) across the ecosystem. Depending on how you look at it, this is either an argument for or against a Base function. If nothing else, it makes the impact of this proposal “bigger”. I’m not sure if this is worth it, but everything I think of is either (a) awkward, and/or (b) includes the code smell of putting type information in the function name (e.g. `pathhandle`).

Thoughts?

---

<div class="post-metadata">

**Author:** ![fatteneder](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fatteneder/32/33991_2.png) [@fatteneder](https://discourse.julialang.org/u/fatteneder)\
**Post date:** [January 13, 2026, 8:48pm UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/159 "2026-01-13T20:48:09Z")

</div>

I have been working on a POC for a virtual file system (VFS) interface package recently, still wip though.  
The central piece here is a `FS <: AbstractVFS` type on which you dispatch all operations.

```julia
import VFS, SimpleFS
VFS.mount(SimpleFS.FS, "mwe.sfs", write=true) do fs
   VFS.mkpath(fs, "/foo/bar/baz")
   VFS.cp(fs, "/foo/bar", "/foo/barbar"; recursive=true)
   VFS.tree(fs, "/")
end

```

```julia-repl
julia> include("mwe.jl")
└─ /
   └─ foo
      ├─ bar
      │ └─ baz
      └─ barbar
         └─ baz

```

I am using good old strings here for paths and I could imagine substituting them with your _slim_ `Path` type.

Inside `VFS` I also work with types like `Dentry` which seem similar to your handle type.  
But to me a `Dentry` (and its friend `Inode`) is an implementation detail for a `VFS.jl` user (altough it isn’t for a FS backend). But I am aware that other languages (e.g. `C++` `<filesystem>`) expose `Dentry`s and their fields.  
I am not strongly opposing the idea for `Base` to define such a resource, but I am not also not sure it’s right to do. What I would rather want `Base` to provide is an `AbstractFile` type to represent opaque file handles and which could be the `handle(::Path)` result, like `FILE` ptr in `C`. We already have `IOBuffer`, maybe with a formal interface it could do the job …

---

<div class="post-metadata">

**Author:** ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)\
**Post date:** [January 14, 2026, 5:32am UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/160 "2026-01-14T05:32:07Z")

</div>

That’s very cool to see. I’m really interested in a design that enables composability, so that (e.g. for testing) you can just swap-in an in-memory filesystem, and it all _just works_.

The two ways I see of achieving this are:

1. Encouraging a new “path calling convention” in Julia, where all path arguments are provided together with a filesystem, e.g. `copy(::Filesystem, ::Path, ::Filesystem, ::Path)`
2. Putting the filesystem + path into one structure. This structure being a path type seems like the best option by far to me, in terms of the simplicity of use as well as the emergent composability.

To make this all work together, I’m now interested in introducing three new top-level (`<: Any`) abstract types/interfaces:

- `AbstractPath`
- `AbstractFilesystem`
- `AbstractHandle`

I’m split between whether `Path` should be abstract or concrete, since it’s certainly the most convenient/obvious type to use in argument types, `foo(x::Path)`. I wonder if in the interests of encouraging more generic code it would be good for this to be an abstract type with system/filesystem specific paths subtype (e.g. `SystemPath`, say).

---

<div class="post-metadata">

**Author:** ![asinghvi17](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/asinghvi17/32/8272_2.png) [@asinghvi17](https://discourse.julialang.org/u/asinghvi17)\
**Post date:** [January 15, 2026, 11:22am UTC](https://discourse.julialang.org/t/designing-a-paths-julep/124335/161 "2026-01-15T11:22:43Z")

</div>

In the earth observation world we often run into file handle limits (iirc the limit is 400 open files on some systems), so I’d be a bit hesitant about making file handles the unit of work. If `AbstractHandle <: AbstractPath` then I would feel a lot more comfortable with doing that - if the user provides a handle, great, otherwise I can defer that until I need to. But it could be I’m misunderstanding what the handle is.

Ideally a handle could also be converted back to a path when it’s no longer needed…

[Previous page](https://discourse.julialang.org/t/designing-a-paths-julep/124335.md?page=7)

[Next page](https://discourse.julialang.org/t/designing-a-paths-julep/124335.md?page=9)
