What is AsRef<Path> + ToString>?

I want to use some function provided by a someone on github:

But as I read the API documentation, I came accross a problem with this function. It states that it takes something that implements AsRef <Path> and also ToString.

pub fn from_path<P: AsRef<Path> + ToString>(path: P) -> File<File>

Here's my code:

let path = Path::new("//192.168.100.8/Receipt Printer").to_string_lossy();
let file = File::from_path(path);
let mut printer = Printer::new(file, None, None);

it throws an error:

error: the trait bound `Cow<'_, str>: AsRef<Path>` is not satisfied
label: required by a bound introduced by this call

note: required by a bound in `escposify::device::File::<W>::from_path`
label: required by a bound introduced by this call

Here's the implementation from the source code that I could get:

pub fn from_path<P: AsRef<path::Path> + ToString>(path: P) -> File<fs::File> {
        let fobj = fs::OpenOptions::new()
            .write(true)
            .create(true)
            .open(&path)
            .unwrap();
        File { fobj }
    }

How should I put my file : "//192.168.100.8/Receipt Printer" in this case ?

You can just put a &str there. Strings implement AsRef<Path>. (You can infer this from the documentaion of AsRef.)

If I do that then another error pops up: type annotations needed cannot infer type for type parameter "W"

That function doesn't have any type parameters called W. Thus, this solves your problem and the other compiler error comes from somewhere else.

As you can see from the source here: device.rs - source
"W" is another interface for io::Write which makes me confused as what this variable should be

impl<W: io::Write> File<W> {
    pub fn from_path<P: AsRef<path::Path> + ToString>(path: P) -> File<fs::File> {
        let fobj = fs::OpenOptions::new()
            .write(true)
            .create(true)
            .open(&path)
            .unwrap();
        File { fobj }
    }

    pub fn from(fobj: W) -> File<W> {
        File { fobj }
    }
}

When you get an error it's very helpful if you post the code that causes it along with the entire compiler error (the one you get from running e.g. cargo check, not the rust-analyzer tooltip) that you get. In this case, the code and full error would look something like this:

    let path = "//192.168.100.8/Receipt Printer";
    let file = escposify::device::File::<std::fs::File>::from_path(path);
    let mut printer = escposify::printer::Printer::new(file, None, None);
error[E0282]: type annotations needed
 --> src\main.rs:3:16
  |
3 |     let file = escposify::device::File::from_path(path);
  |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type for type parameter `W`

which is much easier to help with than type annotations needed cannot infer type for type parameter "W".

You can fix it by specifying what W should be:

let file = escposify::device::File::<std::fs::File>::from_path(path);

The compiler needs this because the functionality of from_path could depend on what W is. In reality it actually doesn't, so the function should probably be in an impl File<fs::File> block instead so that you could call it without caring about W like so

let file = escposify::device::File::from_path(path);
1 Like

Thank you!!!

I'm confused seeing that str implements AsRef<Path>. Following the source code of the cited documentation, the implementation looks like this:

#[stable(feature = "rust1", since = "1.0.0")]
impl AsRef<Path> for str {
    #[inline]
    fn as_ref(&self) -> &Path {
        Path::new(self)
    }
}

Path::new takes an impl AsRef<OsStr> + ?Sized as argument. So apparently str implements AsRef<OsStr>. But isn't OsStr dependent on the operating system? How can I cheaply (and without allocation) convert a UTF-8 string to a UTF-16 string, for example?

Implementation of AsRef<OsStr> for str looks as follows:

#[stable(feature = "rust1", since = "1.0.0")]
impl AsRef<OsStr> for str {
    #[inline]
    fn as_ref(&self) -> &OsStr {
        OsStr::from_inner(Slice::from_str(self))
    }
}

And then I found elsewhere in the source:

#[cfg_attr(not(test), rustc_diagnostic_item = "OsStr")]
#[stable(feature = "rust1", since = "1.0.0")]
// FIXME:
// `OsStr::from_inner` current implementation relies
// on `OsStr` being layout-compatible with `Slice`.
// When attribute privacy is implemented, `OsStr` should be annotated as `#[repr(transparent)]`.
// Anyway, `OsStr` representation and layout are considered implementation details, are
// not documented and must not be relied upon.
pub struct OsStr {
    inner: Slice,
}

Does that mean that current OsStr is just a str internally? But what if that changes? Wouldn't then impl AsRef<OsStr> for str (and thus impl AsRef<Path> for str) need to be removed (which isn't possible because of backwards compatibility?).

But likely I'm just missing some understanding on the internal details. Perhaps someone can explain to me how this works.


Under Unix, we have:

#[repr(transparent)]
pub struct Slice {
    pub inner: [u8],
}

And under Windows:

pub struct Slice {
    pub inner: Wtf8,
}

WTF!?

Also #repr[transparent)] seems to be missing here?


To my current understanding, OsStr isn't UTF-16, but it is (internally) WTF-8 on Windows, allowing to encode unpaired surrogates in addition to the unicode codepoints. Thus, a transformation from UTF-8 to WTF-8 can be done cheaply, right?

However, I still think the #repr[transparent)] is missing, right?


Also, this doesn't concur with OsStr's documentation:

This type represents a borrowed reference to a string in the operating system’s preferred representation.

This isn't (like the documentation suggests) about representation but it's about an OsStr being able to contain data that isn't valid Unicode.

And due to the implementation of AsRef<OsStr> for str, I believe it also can't be made to use arbitrary representation formats in the future. It is "tied" to how str works, even if the operating system "prefers" something else.

I guess the idea is that an OsStr is a platform-defined superset of valid UTF-8. This is an arbitrary byte string in the case of Unix, and UTF-8 + surrogate codepoints (also known as WTF-8) in the case of Windows.

If you have a str, call str::encode_utf16() to get the code units. If you have an OsStr on Windows, call OsStrExt::encode_wide() to get the code units. Collect either of these iterators into your fixed-size working buffer of choice.

In fact, RFC 2295 (os_str_pattern) aims to extend the API surface of OsStr, moving its Windows representation from WTF-8 to OMG-WTF-8 in the process. Incidentally, a recent issue regarding OsStr notes the same thing that you do, that &str to &OsStr must always work.

Yes, WTF-8 is a strict superset of UTF-8, so all valid UTF-8 byte sequences are also valid WTF-8.

You know, you might be right there. Slice::from_str() depends on Slice and Wtf8 having the same layout:

2 Likes

OMG!!!1!

:exploding_head:


Seriously, I'll look into writing an issue regarding the missing #repr(transparent). And also read into the other links you provided.

It's worth noting that because std is std and part of the compiler distribution it can make assumptions about what rustc does that user code cannot. This means that while you couldn't define OsStr this way without #[repr(transparent)], or even transmute::<&str, &[u8]>, stdlib can. (But using #[repr(transparent)] is still considered better practice for std to do.)

1 Like

I don't think this will be possible without dropping impl AsRef<OsStr> for str (and consequently impl AsRef<Path> for str). I don't see yet how this RFC tries to fix that issue.

I added a comment to that issue, regarding that OsStr currently must not just be a superset but also follow the same encoding for all valid strs.

That's so unfair :rage:, I always forget :stuck_out_tongue_winking_eye: (thanks for pointing it out). But still might be worth fixing (or not, depending on design principles behind it).

WTF-8 is a superset of UTF-8, and OMG-WTF-8 is also a superset of UTF-8, so why would this break AsRef<OsStr> for str?

1 Like

Ah, I misunderstood OMG-WTF-8. It's fully backwards compatible. Then it should work.

I filed an issue here:

If there's a good reason why there is no #[repr(transparent)], then the issue can just be closed.