17 ms·
Rust's language ergonomics initiative
- DiegoRamirez 10y agoI forgot, what's Rust's opinion on OO? I would hope it's non-traditional. We need to get away from tradition OO and concentrate on what really matters - dispatch!.
- ww520 10y agoRust has flat level class: Struct and its implementation methods. No inheritance here. Struct method can be called as struct_obj.func1(). Rust also has Trait, which is like interface, with a set of method function signatures. A Trait is implemented for a Struct to have concrete method implementation. Trait supports inheritance.
- Animats 10y agoRust calls them "structs". They're like classes, but different. Rust also has "traits". They're like abstract classes, but different.
- Manishearth 10y agoTraits are closer to interfaces than abstract classes, iirc abstract classes can have fields.
- saosebastiao 10y agoI've noticed this and while I don't use it much it has caught me off guard once or twice. And while it wasn't the end of the world, I wasn't too fond of the solutions I came up with. Is there a reason why they can't have fields? Why wouldn't they just be an "extension" to a struct/enum, modifying their memory representation?
- Manishearth 10y agoRust doesn't do single inheritance, basically. If they had fields, what would you do when you implemented two of them on a struct? It also muddles up the ability to tack on traits in later crates. Traits aren't supposed to be used the way you do single inheritance. Rust prefers composition over inheritance, so you combine structs and enums to get what you want there.
- steveklabnik 10y ago> Is there a reason why they can't have fields? The design isn't done yet: https://github.com/rust-lang/rfcs/pull/1546 https://github.com/rust-lang/rfcs/pull/1546
- Manishearth 10y agoNote that this is different from what's being asked, what's being asked is why they can't have fields like abstract classes that get appended to the original struct. That would make traits more like mixins. That RFC lets you declare fields that the trait implementor is supposed to provide; implementing a trait will not automatically add a field, instead, you will be forced to add such a field yourself (unless one already exists) and link it to the trait field. It's a more rusty way of addressing many of the same use cases, but it's not the same thing. It's basically sugar for declaring a getter and setter as trait/interface methods.
- saosebastiao 10y agoWhile it's not what I asked, I actually like the answer as long as I can provide overrideable default methods in the that use the lvalues. I can't think of any of my own use cases that couldn't adequately be covered by it.
- steveklabnik 10y agoAh yeah, I read your post wrong, my bad!
- 10y ago
- k__ 10y agoStructs are for data and traits are for methods?
- ww520 10y agoStruct is for data and Struct's implementation is for methods. Trait is like interface, just a set of function signatures.
- k__ 10y agoWhy isn't it called interface then?
- paavohtl 10y agoBecause they are traits [1], not interfaces, and they also have some features from type classes [2]. When you create a class in an OOP language, you have do declare all interfaces it supports, and provide implementations for them (or mark the class as abstract). In Rust's case, you declare the data separately as a struct declaration, and then implement the traits you want in impl declarations. You can also implement traits for other traits, or for types from other modules, including the standard library. Finally, compared to traditional OOP interfaces Rust's traits can require static functions in addition to instance methods and they can also implement methods with an overridable default implementation. [1] https://en.wikipedia.org/wiki/Trait_(computer_programming) https://en.wikipedia.org/wiki/Trait_(computer_programming) [2] https://en.wikipedia.org/wiki/Type_class https://en.wikipedia.org/wiki/Type_class
- k__ 10y agoAh, I read the Wiki article and my impression of traits was "interfaces with implementation" and as far as I could tell, Rust traits have no implementation, they 'need' impl(emendations) so they seemed more like interferences to me :)
- steveklabnik 10y ago
- kibwen 10y agoRust doesn't have classes or inheritance. Instead of classes you just have data, and you can give those data types methods via traits, which gives you composition.
- akiselev 10y agoTrait specialization is Rust's answer to several of the abstractions provided in most OOP languages and the initial implementation (with conservative type resolution) has been in nightly for a while. I believe there is still a bit of work to do before stabilization but the feature is under active development. One of the primary challenges is the soundness and predictability of the algorithm that selects the right implementation for each type. With specialization, instead of inheriting classes, traits can provide blanket and default implementations for types that depend on the traits implemented by that type. So, for example, you can provide several different "impl<T> ExTrait for T" with different trait bounds like "T: Clone", "T: Clone + Send", and "T: Clone + Send + Future<...>". Once the conservative inference algorithm is improved, this will be a much more powerful way to compose functionality than OOP but it will require a different approach to abstraction. I think the plan is to eventually add trait objects that implement multiple traits which will cover more complex cases of dynamic dispatch like those in virtual inheritance.
- solidsnack9000 10y agoIt is like Go and Haskell, in that it supports structs and interfaces and that's it.
- pjungwir 10y agoA challenge I've had with Rust lately is factoring initialization code into separate functions. Because of stack-based allocation it has to stay in the main function. For example: pub fn do_many(iter: &mut Iterator<Item=String>) { let mut job_id = None; let job_id_env = env::var("MYAPP_JOB_ID"); let mut log = if let Ok(val) = job_id_env { write_pid_file(&val); job_id = Some(val.clone()); let home = env::var("HOME").expect("HOME must be set"); let path = format!("{}/log/myapp-{}.log", home, val); let path = Path::new(&path); match File::create(&path) { Ok(mut f) => Box::new(f) as Box<Write>, Err(e) => { if format!("{}", e) == "No such file or directory (os error 2)" { Box::new(io::stdout()) as Box<Write> // oh well } else { panic!("Can't open log file: {}", e); } }, } } else { Box::new(io::stdout()) as Box<Write> }; // Commit the tx if we get these signals: let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]); let negotiator = OpenSsl::new().unwrap(); let url = env::var("MYAPP_DATABASE").unwrap_or("postgres://myapp_test:secret@localhost:5432/myapp_test".to_owned()); let tls = if url.contains("@localhost") { TlsMode::None } else { TlsMode::Require(&negotiator) }; let conn = Connection::connect(url, tls).expect("Can't connect to Postgres"); let db = make_db_connection(&conn); // defines a bunch of prepared statements // now we can do stuff . . . } I would really like to have just this: let log = open_log(); let db = prepare_db(); But those don't work, because all the temporary values are going to fall off the stack when the helper functions return. I wish rust were smart enough to make the functions put the values directly in the caller's stack frame. Alternately, I wish rust would let me say that all those temporary values should live as long as the returned thing (log and db), so it can keep them around even if I don't have variables for them. I thought maybe macros would help here, since there is no new stack frame, but they still introduce a new scope that limits the lifetime of the temporary variables. Even worse, if I want to write tests for functions that use the log and db, I need to repeat all that code again and again. I think the answer is to use Box here? I haven't worked that out yet, but it definitely feels harder than it should. And even if I can make it work, I'm a little sad that I have to give up stack-based allocation. I've also read that the answer might be OwningRef (https://crates.io/crates/owning_ref https://crates.io/crates/owning_ref), but I'm not sure yet. I wish the Rust book had a section about it. It seems like Cow and Rc might also help me---I don't think so, but I'm not positive yet. Covering these allocation-related crates in a systematic way would be nice. Anyway, I'm just a Rust newbie, but it sounds like the ergonomics effort is (partly) for newbies like me, so I'm trying to express my struggles in terms of a pattern that the Rust team could optimize for. It seems like something that people would hit quite often. I'm sure there is an answer to what I'm trying to do, so my point is that maybe it should be easier to find, or at least better documented.
- killercup 10y agoI especially like this approach: > Often, the heart of the matter is the question of what to make implicit. In the rest of this post, I’ll present a basic framework for thinking about this question, and then apply that framework to three areas of Rust […] What's proposed here is a universally good way to think about what to make implicit. The proposed changes to Rust are just some applications of this.
- zzzcpan 10y ago"What's proposed here is a universally good way to think about what to make implicit." I had a completely opposite reaction. It ignores all of the important things that make usability good and instead focuses on the approach that essentially promotes inconsistencies in design. "The basic thesis of this post is that implicit features should balance these three dimensions. If a feature is large in one of the dimensions, it’s best to strongly limit it in the other two."
- kbenson 10y agoBased on the actual examples, I'm not sure it promotes inconsistency in design, as long as it's not the sole deciding criteria. As a tool to do first pass exclusion of ideas I think it has a lot of promise. The major problem I see is that it's fairly subjective at the moment in what you consider when thinking about those criteria. For example, in the section about eliminating the need for mod (which was admittedly presented as radical), the following was stated: You could instead imagine the filesystem hierarchy directly informing the module system hierarchy. The concerns about limited context and applicability work out pretty much the same way as with Cargo.toml, and the learnability and ergonomic gains are significant. I think this is a case where the learnability would suffer quite a bit. If I understand it correctly, this changes the filesystem from a unidirectional resource to a bidirectional one, where the presence of arbitrary files not specified (as opposed to a well understood singular file, such as Cargo.toml) might change how the code is interpreted.
- oconnor663 10y agoI think the idea is that `foo::something()` without a module declaration either implies the existence of `foo.rs`, or fails to compile. I'd agree that it would be bad if creating `foo.rs` changed the behavior of code that was previously doing something else (other than breaking the build). What would happen, I think, is that adding a `mod foo { ... }` definition would change the behavior of code that previously implicitly referenced a `foo.rs` file. But that seems less crazy to me, since you've got a change in one file affecting something else in that same file. Or it might make sense for that to be a "conflicting module definitions" error.
- MrF3ynmann 10y agoI still can't get my head around rust. While all those features definitely make sense, I find it very confusing sometimes. Is there something like rust for c++ programmers?
- steveklabnik 10y agohttps://github.com/nrc/r4cppp https://github.com/nrc/r4cppp exists, I haven't read it though. Or at least, not in a very long time.
- pvg 10y agoIs this before or after leafing through their book? Because I think there are at least two stages of not-understanding Rust. One is before taking a look at the book and docs in which you can't make any sense of it at all. Another one is after scanning the book and trying some of the examples in which you really start to understand how you can't make any sense of it at all.
- baq 10y agoit's not really that much c++; try to drop your c++ glasses. might help to pass trough ocaml to collect $200.
- shmolyneaux 10y agoThere is a porting guide for C/C++ [0] that may help clarify the differences between the two. I have not read it though. [0] https://locka99.gitbooks.io/a-guide-to-porting-c-to-rust/content/ https://locka99.gitbooks.io/a-guide-to-porting-c-to-rust/con...
- vatotemking 10y agoCurious, is it harder for c++ programmers to learn Rust than dev from higher level devs? Because ive seen Rust has a lot more success recruiting devs from python, js, even php.
- pjmlp 10y agoNot really, it is mostly a culture thing. There are two main communities in C++, those that embrace safety and take advantage of the language features to improve their productivity, while going down to lower level constructs if performance needs an extra push. Then there are those that are kind of exiled C developers using a C++ compiler, forced to migrate to C++ on their work, trying to use it as C with C++ compiler. This is the group that has more issues with Rust.
- robohamburger 10y ago"Idea: implied bounds" sounds like a very interesting idea. It is a pain copying the bounds as author mentions. I also have worked with library code that does not consistently use trait bounds and it can lead to very confusing errors. The thing that keeps getting me now is there are so many types moving around with generics and traits. It would be nice if it were easier for something to be object safe and/or Any was more powerful. My solution as with many things is to route around it but it is frustrating at times.
- steveklabnik 10y ago> Any was more powerful https://github.com/rust-lang/rfcs/pull/1849 https://github.com/rust-lang/rfcs/pull/1849 is relevant to your interests; however, as the comments say there, associated type constructors would be needed before it could possibly be used with Any.
- robohamburger 10y agoYeah it seems like you would need some kind of guard to make sure you don't violate any of the lifetimes that could be tucked away. Still good work! I guess compiler plugins and libraries will eventually fill in the gaps, until then I guess I just have to be creative :)
- lobster_johnson 10y agoI was surprised that Rust actually did it the way it is now (i.e. no implicit bounds). Article says accessing the map arg's methods will fail, but isn't the entire argument itself invalid by definition? You can't instantiate the hash map without validating that K and V do implement those traits.
- tatterdemalion 10y agoIn the standard library today, there's no bounds on `K` on the definition of HashMap, only on the impl block containing its methods. This sort of makes HashMap a bad example for this code block; you're right that if the bounds were on the definition instead of the impl, the argument itself would be invalid.
- raverbashing 10y agoGreat initiative By its very nature, Rust is harder (than let's say Python or JS). It is compiled, there's not much runtime magic to rely on and low level is hard. But thinking about this and trying to make it easier is important
- the_common_man 10y agoWhat does hard have to do with compiled or not? Visual Basic was actually compiled and it was and still one of the easiest languages to learn ever.
- raverbashing 10y agoVB compiled to a VM that was embedded in the .exe file
- pjmlp 10y agoPCode was only up to version 6. Version 6 introduced an actual AOT compiler to native code. Also its older brother, QuickBasic, compiled to native code. Incidently VB is now again being AOT compiled to native code, via .NET Native and CoreRT.
- EugeneOZ 10y agoIf you are looking for easy magic, Ruby and JS will serve you better.
- the_mitsuhiko 10y agoMy biggest and probably only real frustrating with Rust is that modules and crates live in the same namespace. That makes stuff incredibly confusing to teach and read. I can otherwise live with the explicit extern/mod if needed.
- lohengramm 10y agoI also had problems understanding this whole crate system when I was playing with Rust.
- steveklabnik 10y agoThis is extremely common, and is one of the reasons why it's such a big part of this post.
- EugeneOZ 10y agobut in the post reason of the pain (conventions) described as a cure, and it's really awful.
- keeperofdakeys 10y agoThey need to live in the same namespace, otherwise you'd practically need different syntax for importing something from a crate, or a module. You can use `extern crate foo as bar`, if you are having namespace issues.
- the_mitsuhiko 10y agoYou don't need different syntax. Just prefix all non carte imports with your own crate name.
- no_protocol 10y agoI am not sure if this directly applies to your post, but it came to mind when I read the section about `extern crate`. It seems like the Cargo system is relied upon by almost all Rust users, and I am not sure if the following is an ergonomics problem or a lack of understanding on my part. A couple months ago I was exploring Rust's web server capabilities after seeing the "Are we web yet?" page. I decided to try out the `iron` package.[0] I was quickly able to serve some basic content, but I wanted to add some headers to the response. I was able to `use iron::headers::Allow` to add an Allow header.[1] Next, I wanted a Link header. Link wasn't available but I could get around that by defining a custom header with the `header!` macro. Unfortunately, I couldn't figure out how to get the `header!` macro for custom headers without `#[macro_use] extern crate hyper` and adding `hyper` to the Cargo.toml file.[2] Then I wanted a `Vary` header. I was able to get that in with `use iron::headers::Vary`, but I couldn't actually create one yet! In order to create my `Vary::Items` header, I needed to also `use unicase::UniCase` and add `unicase` to my Cargo.toml.[3][4] So within an hour or so of starting the project, my explicitly listed dependencies had grown from just `iron` to include two additional dependencies. The iron package already relies upon hyper which already relies upon unicase. Here are some questions I am still left with. Would love any responses. Is it possible for me to use the pieces described above without explicitly listing these crates? If not, why do I need to declare hyper as a dependency when iron is already using it? Perhaps I don't need to, and I was just unable to figure out how to get the `header!` macro from iron directly. My initial expectation was that iron would either wrap or expose every part of hyper that I might need. The same goes for hyper not allowing me to just use the same unicase it relies upon. How am I supposed to get the correct version of hyper and unicase to match with the ones that my version of iron was sent with? Do I have to go look them up? Can use the latest version of `hyper` even if `iron` is a few versions behind? What version should I be specifying? [0]: http://ironframework.io/doc/iron/ http://ironframework.io/doc/iron/ [1]: http://ironframework.io/doc/iron/headers/struct.Allow.html http://ironframework.io/doc/iron/headers/struct.Allow.html [2]: https://hyper.rs/hyper/v0.9.9/hyper/macro.header!.html https://hyper.rs/hyper/v0.9.9/hyper/macro.header!.html [3]: http://ironframework.io/doc/iron/headers/enum.Vary.html http://ironframework.io/doc/iron/headers/enum.Vary.html [4]: http://ironframework.io/doc/unicase/struct.UniCase.html http://ironframework.io/doc/unicase/struct.UniCase.html
- 10y ago
- modeless 10y agoAre there plans to do user studies? 10 minutes watching new users code in Rust will give you better ideas than 10 weeks thinking about the problem in your head. I feel like there's a real lack of user testing in software development tools land. If you're developing software for unsophisticated users it's obvious that you should be doing user testing, but it's an often ignored fact that developers are users too! APIs, compilers, build tools, they could all be vastly improved with some user testing.
- steveklabnik 10y agoWe have done informal ones and may or may not do more formal ones, we'll see.
- modeless 10y agoInformal is fine. The important thing is to do user testing in an ongoing way, not just once. Without a feedback loop it's quite surprising how quickly development priorities diverge from what's really important.
- steveklabnik 10y agoWe are always constantly trying to listen to people and get feedback; for example, it's one of the reasons I pay such close attention to these threads! There's always room for improvement, but iterating in this way is defintely a core value.
- IanCal 10y agoDepending on what group you're trying to attract or improve the experience with, you could potentially setup contracts with the level of person you would like to deal with and pay them to implement something in rust while you monitor their progress & with full access to their work and what they're doing.
- vertex-four 10y ago
- CalChris 10y agoAs I've said/posted this elsewhere, the Rust macro package is close to unusable. It makes easy stuff difficult and it doesn't exactly help with difficult stuff. It would be interesting to compare the number of macros defined in the crates corpus divided by total line count and compare that with other languages. I do not think that I am alone in not using it. Yes, I use macros; I just don't program macros. Obviously, Java has shown that you can survive without a macro pre-processor. That was even a point Gosling+Co made in a white paper I read way back in the day. But I do believe that if you are going to have a macro processor, it should be an expedient. Rust's macro processor is not expedient. It is its own impediment. I'm used to using macros. I use them in C and I use them in assembly. These are both low level languages which Rust claims to be. Not being able to use Rust's macros in the style to which I've become accustomed is infuriating.
- steveklabnik 10y agoMacros are being largely re-done, see http://words.steveklabnik.com/an-overview-of-macros-in-rust http://words.steveklabnik.com/an-overview-of-macros-in-rust for an overview. Honestly, I very rarely use macros and have written two in my years of Rust. You almost never need them, or at least, that's my experience.
- CalChris 10y ago#define M_PI 3.14159265358979323846264338327950288
- tedmielczarek 10y agoconst M_PI: f64 = 3.14159265358979323846264338327950288; You wouldn't use a macro for something like that in Rust.
- steveklabnik 10y agoI don't understand what this means. You'd use const in Rust for this, not a macro.
- antiquark 10y agoThey should take on a huge project, like say, converting the linux source code into Rust. The sheer bulk of the code will effectively "force" them to make Rust ergonomic. They might even end up with annoying things, like different sized ints on different CPUs, or... (horror of horrors)... running Rust through a pre-processor as part of its compilation.
- kosinus 10y agoThere's Servo: https://servo.org/ https://servo.org/
- tedmielczarek 10y agoThe Rust compiler is written in Rust, so rest assured the Rust core developers spend a lot of time dealing with the language itself. Additionally Mozilla is spending quite a bit of developer time on Servo, so we have quite a few people actively writing a lot of Rust code.
- kentrado 10y agoOnly 7 or 9 actual developers that work for Mozilla in servo. IIRC.
- johncolanduoni 10y agoWell both of those things basically already exist; you can do conditional compilation based on architecture, etc. and macros can do some of the less hacky things a preprocesser can (as well as many things it can't). The biggest Rust project (which already has many of the core contributors working on it) is Servo, and it's probably about as ambitious as you can get while still having a nonzero chance at completion. I don't think there's much additional value in taking on a completely unfinishable project to shape the Rust language so it's good for a kind of programming (i.e. Kernel programming) that is far removed from what 99% of potential users will use it for.
- kzrdude 10y agoHas the linux kernel made C ergonomic though? I guess there are some gcc extensions that can be the showcase for that.
- alfiedotwtf 10y agoFrom moving from Perl to Rust, the only thing that I miss is the postfix "if": return true if number > 2; VS: if number > 2 { return true } I find that one-liners like these are really ergonomic.
- tedmielczarek 10y agoI have found using conditionals as expressions super useful, like: ``` fn foo(number: u32) -> bool { if number > 2 { true } else { false } } ``` I mean obviously in this situation you could just have the body of the function be `number > 2` but I write a lot of Rust code that does similar things now.
- alfiedotwtf 10y agoyep, that works... but having it postfix affords salience
- kbenson 10y agoIn Perl, it's fairly idiomatic to use a postfix condition on a return like that when doing early return. Some of that is obviated by Rust being typed, some is not. e.g. sub compute_interest( $amount, $interest ) { return $amount if $interest == 0; # Quick return die "We don't allow computation of negative interest rates" if $interest < 0; # Throw an exception # Do the actual work ... } Edit: Also, it's worth noting that Perl enforces some behavior on this by only allowing postfix conditionals to follow a single statement, not blocks, so it's not just a regular conditional with the order reversed.
- kazagistar 10y agoI've always disliked this aspect of Perl... I prefer control flow to be obvious.
- AstralStorm 10y agoHiding the actual control part on the right is pretty bad, yes. Other than that, early returns can simplify flow a lot - otherwise you may have to do massive nesting ifs or many flags. Or even goto or exceptions.
- hinkley 10y agoI'm an ergonomics nut and I've been looking to learn Rust. Any chance they're looking for a set of guinea pigs to report their experiences as new users, or are they mostly working on already-known issues?
- steveklabnik 10y agoWe are always looking for experience reports. Making a post on internals.rust-lang.org would be great!
- deleted 10y ago[deleted]
- swuecho 10y agoGreat! but do not forget to document what is implicit. Otherwise, it is magic and make it more confusing. That is the impression of my last attempt to learn rust.
- raz32dust 10y agoI hope the ergonomics initiative takes it towards Java rather than Python. For all the hate it gets, Java's explicitness is a boon when maintaining large scale systems and preventing bugs.
- actuallyalys 10y agoWhat parts of Java are you thinking of? When I think of explicitness Java has but Python doesn't, types mostly come to mind, but Rust is already statically typed, so that can't be what you mean.
- DiegoRamirez 10y agodevelopment "ergonomics is something close to my heart. I'm a statically-typed, "easy-on-the-eyes" Python looking-loving guy, but there's something beyond that, when it comes to "ergonomics". I'm really impressed with the Clojure community and parinfer, paredit(old lisp school) and just slinging code around with rapid feedback.
- twic 10y agoI'm really encouraged by this post. I ran into a situation somewhat related to the borrowing in match patterns this week [1], and whilst it's only a mild annoyance, it's lovely that it might get smoothened out. Today, i started using modules in anger, and was immediately mildly annoyed by the need to explicitly reference crates in my code, when they're already in my Cargo.toml, and to declare modules, when they're implied by my file structure, so i'm happy to see that that is on the radar too! The file structure one makes me laugh, because one language that does implicitly create modules from file structure, in exactly the way Rust would need to, is Python, which is the one with the whole "explicit is better than implicit" deal! [1] https://www.reddit.com/r/rust/comments/5whke7/deref_coercion_in_an_if_let/ https://www.reddit.com/r/rust/comments/5whke7/deref_coercion...
- EugeneOZ 10y agoplease fork the language and call it somehow RustyRuby if you prefer things to be implicit.
- Tarean 10y agoThe implied bound one reminded me of a very similar thing in haskell https://prime.haskell.org/wiki/NoDatatypeContexts https://prime.haskell.org/wiki/NoDatatypeContexts . Basically data Hashable a => Set a = ... is completely useless. It only forces you to add constraints to functions that, if necessary, would be required anyway. Not to be confused with existential quantification data ExistentialSet a = forall a . Hashable a => ... which carries a reference to the hash function in the instances, similar to trait objects in rust.
- tatterdemalion 10y agoDatatypeContexts turns on a feature that Rust already has - constraints on type parameters to datatypes. This is about propagating constraints from the datatypes to functions over it. Haskell hasn't found DatatypeContexts very useful, but Rust has. In my opinion this is largely because of the difference in what our type systems mean - in Rust, types carry a lot more information about the memory model & data layout than in Haskell. This has led to a different skew. In Haskell, DatatypeContexts also create struggles with higher kinded polymorphism (you can't implement Functor for the definition of Set you just provided, for example), but in Rust, the same memory model concerns that make datatype constraints useful make traits like Functor less useful.
- k__ 10y agoPython and Nim are good examples to learn from ;)
- trento 10y agoI was able to work with some rust developers at Hack Illinois recently. We started the 2017 Rust cookbook [0] with Brian Anderson. [0]: https://github.com/brson/rust-cookbook https://github.com/brson/rust-cookbook
- killercup 10y agoVery cool! Took me a while to find the rendered version, though: https://brson.github.io/rust-cookbook/ https://brson.github.io/rust-cookbook/
- yarper 10y agoAfter writing Rust in production for a while, the biggest bugbear I have is the naming/file structure. I end up a lot with this; src/main.rs src/combobulator/mod.rs src/combobulator/tests.rs src/tests.rs src/somethingelse/tests.rs src/somethingelse/mod.rs Because I find tests in the same file a bit confusing. It's really easy with maven-style layouts to know that "only things in main/java or main/scala get compiled and go into the jar". "src/test/*" and "src/main/resources" are for me. The same thing applies for cargo.tomls and resources - there's not really a way to see what goes into the executable from the file structure. But this isn't the biggest problem with having things called "mod.rs". That would be if I open 5 mod.rs's in a text editor with tabs, I have no idea what goes with what. I know that tests should go under tests/, but that's specifically for integration tests. Integration tests are an order of magnitude less likely to get written imo, and if they are they'll probably get written as unit tests anyway. If anyone has any top tips for how to structure larger Rust projects while separating unit tests into different files, please let me know!
- dogamak 10y agoAdditionally tests in "tests/" are compiled to independent binaries on which the test suite is run on separately, which results into verbose and redundant output when ran. Also if you keep your tests under "src/", you can't just stick them wherever you want; you need to have #[cfg(tests)] on every module declaration leading to the test function, otherwise the test is ignored silently. From time to time I find old tests that have not ran once because of a missing attribute somewhere upwards the module hierarchy.
- steveklabnik 10y agoHm, cfg(tests) should only control if they're included in non-test builds; it shouldn't ignore them entirely.
- JoshTriplett 10y agoI prefer to keep tests in a `mod tests { ... }` block at the end of the source file, which provides comparable benefits and separation. I also prefer to use `combobulator.rs` rather than `combobulator/mod.rs`, for multiple reasons including filename ambiguity. In this case, you'd have: src/main.rs src/combobulator.rs src/somethingelse.rs
- spraak 10y ago> Right now, such a signature would be accepted, but if you tried to use any of map’s methods, you’d get an error that K needs to be Hash and Eq, and have to go back and add those bounds. That’s an example of the compiler being pedantic in a way that can interrupt your flow, and doesn’t really add anything; the fact that we’re using K as a hashmap key essentially forces some additional assumptions about the type. But the compiler is making us spell out those assumptions explicitly in the signature. I feel this exact same way with Go. E.g. x := map[string]map[string]int{ "key": map[string]int{ "another": 10, }, } Given that the outer type signature says that the `value` of the map should be a `map[string]int` it's sometimes quite annoying to specify that inner type over again
- zeeboo 10y agoYou can leave the inner type out... https://play.golang.org/p/m3bLmneArB https://play.golang.org/p/m3bLmneArB
- spraak 10y agoAh, maybe it's when they're alias types that that wouldn't work?
- HeyImAlex 10y agoActually I just tried that and it worked also. But I think you're right that this hasn't always been the case because I remember being annoyed about it. Or maybe we're both crazy. You definitely need to specify the type for an interface, but that makes sense.
- unscaled 10y agoYou're not crazy. I also have vague memories of maps being more obnoxious in Go. :) The facts are that map literals did change a bit in Go 1.5 (i.e. fairly recently), full literal specification for values could already be elided back then - it was KEYS that required full specification: https://golang.org/doc/go1.5#map_literals https://golang.org/doc/go1.5#map_literals If you go way more back, Go 1 also saved some typing on values, in case they were pointers: https://golang.org/doc/go1#literals https://golang.org/doc/go1#literals
- StephenAshmore 10y agoI've been writing Rust quite frequently recently, and have enjoyed it. However, the biggest problem I've faced so far is exactly what they are trying to address. Modules can be annoying, and I wouldn't mind better optional parameters or a better way to box up trait objects.
- ww520 10y agoSpeaking of removing friction, there are three areas that have caused me grief when I wrote Rust code: 1. Error handling. The lack of built-in support for multi-error or error union in Result is painful in dealing with different types of error in a function. Support for Result<Value, Error1 | Error2 | Error3> would be helpful. Or may be support for easily converting one type of error to another. Now there's lots of boiler plate code to deal with error conversion. Error chaining would be nice, too. 2. Lack of stack trace when an error occurs. Now that stacktrace starts when panic!() is called, which is kind of late. 3. Better support for conversion between &str and String. Dealing with strings is so prevalent in programming that making it easier to work with the two types would be a huge boost to productivity. Edit: another item 4. Support of partially applied function , i.e. bind a subset of arguments to the function pointer. Currently there's no way to bind the self argument to the Option/Result chaining calls. Basically the Option/Result chain (.and_then, .map, etc) only carries forward the value of Option/Result and nothing else. It would be nice put partially applied function in the chain. e.g. result.and_then(self.func1) where func1 has the self argument bounded. Or in more general form, result.and_then(func1("param1", param2, _)) where func1's first and second parameters have been bounded up front and the value of result will be passed in as the 3rd parameter.
- yarper 10y agofor 1. there's map_err(..) which accepts a closure e.g. `try!(foo().map_err(|e| format!("{}", e.thing)));`
- JoshTriplett 10y ago> 1. Error handling. The lack of built-in support for multi-error or error union in Result is painful in dealing with different types of error in a function. Support for Result<Value, Error1 | Error2 | Error3> would be helpful. Or may be support for easily converting one type of error to another. Now there's lots of boiler plate code to deal with error conversion. Error chaining would be nice, too. There are a couple of crates that support this; personally, I recommend the "error-chain" crate. However, I do wish that Rust promoted the most capable of those to the standard library. > 2. Lack of stack trace when an error occurs. Now that stacktrace starts when panic!() is called, which is kind of late. error-chain provides that. > 3. Better support for conversion between &str and String. Dealing with strings is so prevalent in programming that making it easier to work with the two types would be a huge boost to productivity. Can you give some specific examples of cases you've found cumbersome? String has a Deref instance for &str, so taking a reference to a String automatically works as a &str. You can also call .as_str(). Going in the other direction, you can call .to_string() to make a copy of a &str as a new String.
- EugeneOZ 10y agoCan't trust such ideas. Especially when I read about "conventions are good". No, "conventions" is the plague and that example of mod.rs is an example of crappy thing in Rust, difficult to gasp by a newbie. Exactly because it's just "convention". Hope they will not destroy Rust by adding more "conventions" or by switching to " implicity over explicity" camp of noobs.
- zengid 10y agoA bit of inspiration can be gleaned from the work of Dr Stefik on Evidence-based language design [1][2]. [1] https://www.youtube.com/watch?v=uEFrE6cgVNY https://www.youtube.com/watch?v=uEFrE6cgVNY [2] http://dl.acm.org/citation.cfm?id=2534973 http://dl.acm.org/citation.cfm?id=2534973
- steveklabnik 10y agoThank you for this link! I'd been trying to find this again...