Suppose you want to read lines from a file named foo.txt. Since a string literal &str is not the same type as a &Path, you'd have to convert your filename explicitly using the AsRef<Path> implementation:
let lines = read_lines2("foo.txt".as_ref())?;
With the generic version, you can pass anything as an argument that implements AsRef<Path> and the function will take care of calling .as_ref() for you, so you can write simply
let lines = read_lines("foo.txt")?;
(Technically this is using the AsRef<Path> impl for &str, which comes from this blanket impl, rather than the one for str.)
I think I get you know, still trying to get used to Rust, so why wouldn't this work?
The error message is that filename doesn't have a szie known at compile-time. This is a great feature of Rust, but will take some getting used to coming from a c# background.
Unlike languages such as C# and Go, an interface doesn't actually exist at runtime so you can't pass around a bare trait like that in Rust. A trait is just a contract which says a type will have a particular set of behaviour.
That means you need to either make your code generic (fn read_lines<P>(path: P) where P: AsRef<Path> or fn read_lines(path: impl AsRef<Path>)) or use something called a "trait object" (written as dyn AsRef<Path>) which is a special struct that the compiler generates for doing dynamic dispatch and must always be behind some level of indirection.
The underlying reason for this P: AsRef<Path> generic's existence is to paper over the complete mess that is filesystem paths and "strings" across different operating systems. By accepting any P which implements AsRef<Path> your read_lines() function can be given anything that is convertible to a path and it'll Just Work.
It's really convenient to use Rust's UTF-8 strings and string literals (String and &str) for dealing with the file system, however it turns out that UTF-8 is too strict and both unices and Windows can support a larger (incompatible) range of "strings". We don't want to make certain things inaccessible so we need a way to express this.
The OsString and OsStr types exist to represent strings which are valid on the current OS but possibly not valid Rust strings, and the PathBuf and Path types exist as strongly-typed wrappers around their OsStr counterparts so you can have nice methods like my_path.exists() and my_path.join("folder"). Path and OsStr can't be combined into a single type though, because you use strings for more than just filesystem paths when talking to the OS (e.g. environment variables are OsStrings).
This is one example of where Rust has been bitten by this in the past:
There's also CString for working with C's null-terminated "array of bytes" strings, but let's not talk about that.
In other languages AsRef<Path> does not exist. Their "Path-like" abstract types are equivalent of Rust's Box<dyn AsRef<Path>>. fn read_lines(path: Box<dyn AsRef<Path>>) would work, but Rust wants to be more efficient than this and avoid allocation and dynamic dispatch.