`anyhow` conflicting with `Rc`

Hello,

I have a type Span, which represents a region in a file, and is supposed to hold some data about that region: its location, the file where it belongs, the actual content of that region, maybe even the content of the neighbor lines, ... Since I want to build a lot of these spans, since a lot of them will most likely have overlapping data, and since they never need to modify the data they hold, just to read from it, it makes sense to share the content of a file among all the Spans that are created while reading it. The problem is that there is no clear owner of that data: the function that reads the file and produces all the Spans can only own that data for so long; no Span should own data, since it isn't be clear which one should own it; there is no global state where I can just put all the data in and pass it around.

For this particular situation, it seemed to me that the most appropriate choice was an Rc, since that's exactly what it provides: readonly shareable data with no clear owner. So for instance I have some fields with Rc<str> or Rc<Path>.

Of course, when I create errors, I happily include the span of the concerned objects (that's why I keep them around, to produce nice error messages) in the error, and so far there is no problem.

The problem arises when I write some driver code for my library, and I try to use anyhow. anyhow's Error requires Send + Sync, which rules out my error type, and so makes anyhow unusable.

I could turn my Rc into Arc, but that strikes me as very wasteful: most of the Spans I will create will not be included in an error, and yet each of them have to pay the overhead of Arc.

I could also "take ownership" in the error, that is, in the error, instead of having a Span, I could have something like an OwnedSpan which would just turn all the Rc<str> into String and all the Rc<Path> into PathBuf, because one might argue that errors should be self-sufficient. And yet, I'm not convinced by this solution, among other things because that would require almost duplicating the code for Span. Then I thought that maybe I need a trait Spannable or something like that to avoid code duplication, but it looks like I'm missing the easy solution and that I'm trying to build something overly complicated.

On final other solution that was proposed to me was to use a Cow<str>, but I don't think it fits this problem. At least, in my mental model of how Cow works, it wouldn't be able to share pieces of a string.

So the question is: what is the proper way to deal with anyhow? Can I, in some way, make it relax its Send + Sync condition, that is completely useless for me (I have no threading, no asynchronous code, nothing alike). Is there another smart pointer that could solve this problem?

In your error type, you could wrap the span with something like fragile which adds Send and Sync implementations, but will error/abort/leak if you actually try to access the data from another thread.

1 Like

This is probably much less than you expect; atomics are very cheap on most platforms and you only incur this "overhead" when Arcs are created/destroyed, not when you're passing them around.

There's also another alternative; just make your error type hold a PathBuf/String (or use anyhows .context thing), and create that when needed. In most programs the error path is hit very rarely or if it happens the error is just bubbled up all the way, so performance doesn't matter anymore.

6 Likes

Thanks, that's definitively the option I'll use, unless an other solution (better) is provided in this thread.

Sure, but it's quite ugly. Because I can't solve an issue at compile time, I have to pay more at run time.

That's, unfortunately, impossible: it's not my error that holds a PathBuf or a String, it's my Span (whose only purpose is to print messages for the user, including error messages); my error only holds a Span. The only workaround to make errors own their data would be to create an OwnedSpan which would be the owned counterpart to Span, and the error would take the owned version. But that's heavy, as I mentioned in my OP.

There's also a third solution; just don't use anyhow.