7 ms·
I like Rust, and I'm probably missing something, but do I feel that some things should be easier. At times the language (or standard library, I suppose) feels
by ilitirit 12y ago
I like Rust, and I'm probably missing something, but do I feel that some things should be easier. At times the language (or standard library, I suppose) feels a bit unwieldy. For example, trying to coerce a string from certain types:
let path = Path::new(&app_path)
println!("file name: {}", path.file_name().unwrap().to_str().unwrap());
Hopefully this is just a case of unfamiliarity, but surely something like should be easier?
- Mr_T_ 12y agoThe 'String', '~str' dichotomy is really unfortunate.
- Jweb_Guru 12y agoThere is no ~str, but when there was it was largely identical to what String is now.
- pcwalton 12y agoPresumably you mean String/&str? (~str hasn't existed for a long time.) On the contrary, the lack of the string/string-view distinction is widely considered a flaw in the C++ standard libraries. C++14 introduces std::string_view to correct it. Rust's String/str distinction is just the same as C++14's std::string/std::string_view.
- frowaway001 12y agoApart from the Rust's usual rule to invent some abbreviations, but only apply them half the time. Then "types start with an uppercase letter" except when the language creators break their own rule. Then the fun "sometimes we use () and sometimes we use [] to do practically the same thing". Then the various insanities with ::. Then Generics. How many examples of languages which tried to use <> for Generics do we actually need before people learn the lesson? I really wished some people took Rust, and wrote a new frontend to get rid of all the pointless inconsistencies and other "C did, so it has to be good"-isms (like_all_this_underscore_stuff or the ridiculous abbreviations), because many ideas of Rust are not only good but brilliant and deserve to be put into languages which don't belong to the purely-academic category. I really wish Rust to succeed, but can we stop this approach of "3 steps forward and 2 steps backward" to language design?
- pcwalton 12y ago> Apart from the Rust's usual rule to invent some abbreviations, but only apply them half the time. Do you have an example? > Then "types start with an uppercase letter" except when the language creators break their own rule. They always start with a capital letter, except for built-in types or FFI bindings to libraries such as libc where the original types used a lowercase letter. This convention is exactly the same as Java. > Then the fun "sometimes we use () and sometimes we use [] to do practically the same thing". I've never heard this before. Do you have an example? () is used for function calls, grouping, and tuples, while [] is used for array literals and array indexing. > Then the various insanities with ::. The double-colon is very useful so that you can tell the difference between module lookup and field projection/method calling. Early Rust used . for module lookup, and it was confusing to tell at a glance whether a function was being called or a method was being invoked. > Then Generics. How many examples of languages which tried to use <> for Generics do we actually need before people learn the lesson? Using square brackets like Scala wouldn't actually help with the ambiguity, because it would be ambiguous with array indexing. The only syntax I've seen that actually solves the ambiguity is D's `!()`, which was deemed too unfamiliar. Angle brackets were chosen because they are familiar and aren't really any worse than the other mainstream options. > I really wished some people took Rust, and wrote a new frontend to get rid of all the pointless inconsistencies and other "C did, so it has to be good"-isms (like_all_this_underscore_stuff or the ridiculous abbreviations) The underscore naming convention was actually taken from Python's PEP 8. Rust doesn't really have any more abbreviations than most other languages at this point.
- callahad 12y ago> > Then the fun "sometimes we use () and sometimes we use [] to do practically the same thing". > I've never heard this before. Do you have an example? () is used for function calls, grouping, and tuples, while [] is used for array literals and array indexing. Maybe referring to the ability to use either type of brace with macros?
- pcwalton 12y agoYeah, you can use any type of delimiter with macros, but I think that's an important feature, especially since macros can expand to declarations. Early versions of Rust required parentheses for all macro invocations, and that looked really ugly for anything that looked like a block. Delimiter interchangeability is a feature from Racket that works really well when it comes to macros and I'd hate to give it up.
- mbrubeck 12y agoIn this example the dichotomy is between String (which is guaranteed by the type system to be valid UTF-8) and OsStr (which might be in an unknown encoding or otherwise not decodable to valid Unicode). This is exactly when you want a systems language to require explicit conversions, rather than converting things silently and possibly losing or corrupting data.
- davvid 12y agorather than converting things silently and possibly losing or corrupting data Exactly. Python3 went down the "silently converting" route, and it's not pretty[1]. I would go so far as to call it harmful. http://lucumr.pocoo.org/2014/5/12/everything-about-unicode/ http://lucumr.pocoo.org/2014/5/12/everything-about-unicode/ I understand the difficulty in this space; much of it is caused by forcing the Windows unicode filesystem API onto python as its world-view, rather than sticking to the traditional Unix bytes world-view. I'm unixy, so I'm completely biased, but I think adopting the Windows approach is fundamentally broken.
- Veedrac 12y agoThe problem there is overblown - it's basically all due to the idea that sys.stdin or sys.stdout might get replaced with streams that don't have a binary buffer. The simple solution is just not to do that (and it's pretty easy; instead of replacing with a StringIO, replace it with a wrapped binary buffer). Then the code is quite simple import sys import shutil for filename in sys.argv[1:]: if filename != '-': try: f = open(filename, 'rb') except IOError as err: msg = 'cat.py: {}: {}\n'.format(filename, err) sys.stderr.buffer.write(msg.encode('utf8', 'surrogateescape')) continue else: f = sys.stdin.buffer with f: shutil.copyfileobj(f, sys.stdout.buffer) Python's surrogateescape'd strings aren't the best solution, but I personally believe that treating unicode output streams as binary ones is even worse.
- doomrobo 12y agoThat would be: let path = Path::new(&app_path); println!("file name: {}", path.display());
- ilitirit 12y agoNo, I just want the file name.
- steveklabnik 12y agoYes, it's true. Code like this is going to be more verbose. Often, it's due to Rust being a systems language and exposing low-level details to you, combined with the type system ensuring safety. In this instance, `Path` is actually fairly complex. From the RFC: https://github.com/rust-lang/rfcs/blob/master/text/0474-path-reform.md https://github.com/rust-lang/rfcs/blob/master/text/0474-path... > The design of a path abstraction is surprisingly hard. Paths work > radically differently on different platforms, so providing a > cross-platform abstraction is challenging. On some platforms, paths > are not required to be in Unicode, posing ergonomic and semantic > difficulties for a Rust API. These difficulties are compounded if one > also tries to provide efficient path manipulation that does not, for > example, require extraneous copying. And, of course, the API should > be easy and pleasant to use. So, in this case, you have to call `file_name` to get the filename, but that returns an `Option`, since it might not be a file, it could be a directory. The `unwrap` says "crash if it's not a filename." You then have an `OsStr`, which is a string type that handles the kinds of cross-platform issues alluded to in the above paragraph. `to_str` will then attempt to turn it into a `&str`, but because `&str` must be unicode, if the conversion fails, it _also_ returns an Option, which is the last unwrap. In systems programming, details matter. As such, our stdlib APIs try to expose those details. I expect some people will write higher-level abstractions that hide all this if you don't care as much. Tradeoffs tradeoffs tradeoffs...
- deleted 12y ago[deleted]
- jerf 12y ago"Often, it's due to Rust being a systems language and exposing low-level details to you, combined with the type system ensuring safety." I'm not convinced it's being a "systems language" (in the sense I think you mean). The last three languages I've learned to fluency have all, in various ways, been very concerned about errors: Erlang, Haskell, and Go. Yes, they have widely varying opinions about what to do with them, but they all agree that you have to be fairly constantly worried about them. You can't sweep them under the rug; you need a strategy, and you need to deal with the resulting issues head on. Despite their differences, what I feel like I've learned from them is that historically speaking, the vast majority of popular languages and their communities have been downright cavalier about errors. It's often in all the little details rather than a consistent, stated design goal, but the presence of various nils and nulls, APIs that will happily return empty strings for paths instead of errors, and all kinds of APIs and libraries that seem to be subconsciously written with the belief that returning an error is a failure on the part of the library and will go to what now seem to me to be absurd lengths to somehow keep klunking along in the presence of already-manifested failure. Even for languages that think they're being really good with exceptions, I find that when I go back to them, they end up using the exception support to "permit" themselves to be sloppy and hope the exceptions will pick up the pieces... and they often don't. Not because of some theoretical weakness, but because allowing developers to think they don't have to think about the error cases means precisely that they don't! People are used to errors not poking them in the face until deploy time. It seems to me there's a strong, broad motion towards developing languages that will in one way or another force you to deal with them at compile time. It's a culture shock for people, but, I'll tell you this, get really good at Rust or any of these other languages and go back to any of those other languages, and you will find your code changes in those other languages to suddenly look a lot more like your Rust does anyhow, which means this putative disadvantage of Rust evaporates anyhow. Once you see how sloppy you were being, it's hard to go back in good conscience.
- deleted 12y ago[deleted]
- Jweb_Guru 12y agoIf you were willing to allow for potential escape characters and acknowledge (to the user) the fact that the path might not have a file name, you could write: println!("file name: {:?}", path.file_name()); or just for escape characters, but only if the file name existed: if let Some(name) = path.file_name() { println!("file name: {:?}", name); } Note that the unwrap() isn't boilerplate, in either case: if you are using `unwrap()` you are implicitly saying that you know for a fact that the file name is present (the first time) and a valid UTF-8 string (the second time), which isn't guaranteed in the general case on all platforms. If you wanted to write this more robustly, and these weren't guaranteed, you would need to explicitly take care of those cases. I/O has lots of tricky error cases, and Rust usually requires you to acknowledge that. You can avoid dealing with those cases with unwrap(), or you can explicitly handle them. Personally, I much prefer this to being surprised only when the weird error case unexpectedly turns up because I never considered the situation in question, and that's generally how Rust handles things, but it depends on the task. It may make Rust less appropriate for quick scripting tasks, for example, where the domain is well-defined and the script is not likely to be reused (but I find that those scripts have an annoying way of getting reused anyway).
- drchickensalad 12y agoHowever, rust would be relatively well-suited for a "dirty quick scripting task". You could make a more traditional wrapper for the operations you like to use that assumes these rare edge cases don't happen, and panic otherwise. This is sort of rust's philosophy; being explicit. Rust has no problem with failing, it just has a problem with failing without you telling it to. If you write a wrapper that implies you are willing to fail on these cases, then you're being explicit by choosing to use these functions instead :)
- phaylon 12y agoI would say it is familiarity, but in another sense. You can use something like path.file_name().and_then(|f| f.to_str()).unwrap() but that really doesn't buy you much. Where familiarity comes in I believe is in what that line communicates to me: There might not be a file name (like having '/' as path), and that getting a string slice out of it might fail (the kind of string the platform uses for paths might not be UTF-8 compatible). Plus, the unwrap()s tell me that whoever wrote the code considered it an assertion failure if one of those is happening. I read a lot more Rust than I write, and it really helps that the code communicates a lot. And with knowing about OsString/OsStr beforehand, I only had to lookup when file_name() returns None, the rest is rather implicit by convention, or familiarity.
- tikue_ 12y agoI wish `and_then` were named `flat_map` so badly.
- Manishearth 12y agoWe have `flat_map` for iterators. (`and_then` works for Options, which are basically nullables) You can convert an Option to a zero-cost iterator and then use `flat_map` though.
- Manishearth 12y agoTo add to the other comments, Rust prefers to make error cases explicit. We have memory safety. You can opt out of it, but you have to opt out explicitly. We have exhaustive match. You can use a wildcard, but you need to explicitly do so. We have nullables and Results. You need to explicitly acknowledge the error/null case, or use `unwrap()` to say that you don't mind a panic at this point. This leads to verbosity at times, but usually its a good verbosity.
- mrec 12y agoI'm sure I'm not the first (or the hundredth) to say this, but `unwrap` has always struck me as a horribly innocuous spelling of "yes, feel free to crash here". I presume there is (or could be) a standard lint to prevent this getting into production code, but it still feels kind of icky.
- steveklabnik 12y agoWe almost changed it to `assert` but that made some people _really_ upset.
- rat87 12y agoDammit, I kept wondering why it wasn't named unwrap_or_panic/unwrap_or_assert but it could be worse at least unwrap is a less friendly name then scala's get.
- deleted 12y ago[deleted]
- Manishearth 12y ago`.expect()` is slightly better (you can give it a string that will become a part of the panic message)
- masklinn 12y ago> surely something like should be easier? That would be nice, but as others hinted that wouldn't actually work: * there are paths without a file_name (`/`) so it returns an Option to make that evident * to_str attempts to convert the OS-dependent thing to a Rust string. Rust strings are valid unicode, but many filesystems don't have such strict requirements[0]. Again Rust returns an Option to makes that evident although there's a "to_string_lossy" method which will simply replace invalid sequences with U+FFFD if you don't really care The Rust philosophy is generally to expose these issues upfront and force the developer to consider them (and either handle or explicitly ignore them). It is indubitably less convenient than e.g. Python 3's `PurePath(app_path).name` returning an str, but you'll have had to decide what happens in case of failure. Paths are really a good example of something which looks simple, should be simple, and is actually anything but simple for historical and technical reasons, especially in a cross-platform context. [0] * Unix paths are the most troublesome: they're arbitrary bags of (non-NUL) bytes, they don't have to be in a known encoding, they don't have to be in any encoding. * Windows paths are not guaranteed to be Unicode either: they're UTF-16 code units aka UCS2 + surrogates, and the surrogates may be unpaired, leading to invalid Unicode. * OSX paths are guaranteed to be valid Unicode.
- WallWextra 12y agoAlso, OSX applies unicode normalization to the filenames, which can cause some interoperability problems with unix, e.g. you try to copy files forth and back between the systems and end up with two copies of a file, with identical-looking filenames that differ in their unicode normal form.
- enjolras 12y agoJust my 2cs, but the complexity of this code is here to remind you there are a lot of cases you have to take into account : let path = Path::new(&app_path); if let Some(ostr) = path.file_name() { if let Some(str) = ostr.to_str() { println!("file name : {}", str); } else { println!("WARNING : file name is not a valid unicode sequence ! (file name : {:?}", ostr); } } else { println!("Path is either a root directory or a dot entry, it has no filename"); }
- rat87 12y agoI think unwrap calls are mainly meant for quick and dirty stuff and if you find yourself doing it it may be the wrong solution.