9 ms·
Rust: A Scala Engineer's Perspective
- saghm 9y ago> When handing out references of something bound with let mut, why do i need to do &mut instead of just & ? Sometimes, you still want to immutably borrow a mutable variable. Otherwise, you wouldn't be able to have multiple references to a variable declared with `mut`, which would be an unnecessary limitation. EDIT: Here's an example of something that wouldn't otherwise work if borrows of mutable variables were implicitly mutable: struct Point { x: i32, y: i32, } fn add(p1: &Point, p2: &Point) -> Point { let x = p1.x + p2.x; let y = p1.y + p2.y; Point { x: x, y: y } } fn main() { let mut some_point = Point { x: 1, y: 1 }; some_point.x = 2; // `some_point` is borrowed twice here, which wouldn't be possible if the borrows were mutable. let other_point = add(&some_point, &some_point); println!("({}, {})", other_point.x, other_point.y); }
- skywhopper 9y agoMy best interpretation of his question was more along the lines of: why do you have to mark it as `mut` if you aren't going to change it in the function? Suddenly your function only accepts mutable pointers, not just any pointer.
- eridius 9y agoWell, you wouldn't. The example function in the blog post isn't mutating the argument, but that's why the comment says // This uses mut for no reason other than to prove a point. You wouldn't normally have a function accept a mutable reference if it's not mutating it.
- jackmott 9y agoit would be a compiler warning if you did, too.
- kibwen 9y agoRust deliberately doesn't do interprocedural analysis, so function signatures are the ultimate source of truth. If you want to call a function that takes a `&mut` then all the rules for taking a `&mut` apply. If the function that takes a `&mut` doesn't need a `&mut`, however, then that's probably just bad API design, and is something to be avoided (I've never seen any such thing in the wild; the guideline in API design is to make your function signatures as unrestrictive as you can, and `&mut` is as restrictive as it gets).
- kevincox 9y agoTo me it lets me know if an object will be changed by the function or not. If it's just & I know that my object is just how I left it. If it is &mut I need to think about how it was changed.
- scottlamb 9y agoTo the compiler, too. if it's just &, the compiler knows the pointee is just how it left it[1], and it can optimize on this assumption. For example, if it had the contents in a register before the call, it knows they're still valid after the call. This is the same sort of thing "restrict" gives you in C, but unlike "restrict", using & is idiomatic and the compiler will spot mistakes. If it's &mut, those assumptions aren't valid. [1] except, iiuc, the contents of UnsafeCells (and things built on top of them, such as RefCell).
- kevincox 9y agoWell the compiler knows the signature of the function you pass it to so it should be able to optimize it anyways. I think in most if not all cases the compiler can figure out if &thing needs a mutable or immutable reference. I think the only difference is the readability to the programmer and I agree with the decision made requiring &mut for a mutable reference.
- steveklabnik 9y ago> so it should be able to compile it anyways. &T vs &mut T is very significant in Rust; not only the stuff that the parent brought up, but also variance. It's not a simple lint.
- kevincox 9y agoSorry, I typed the wrong word. I've updated the parent to be less confusing. s/compile/optimize/ For reference the first line used to be: "Well the compiler knows the signature of the function you pass it to so it should be able to compile it anyways." > &T vs &mut T is very significant in Rust Yes, especially for function signatures it is critical, however the ancestor was talking about using & to take a reference. When using & to take a reference I don't think &mut needs to be required. The compiler could probably figure it out.
- kllrnohj 9y agoThe question isn't why does the function decleration need to have &mut, it's why does the call site need to have &mut? as in assume you had: fn reset(p: &mut Point) { p.x = 0; p.y = 0; } Why can't this be called like so: reset(&some_point); rather than needing to say reset(&mut some_point); To some extent it's nice that the callsite must document the behavior visually, but its more forcing a style guide rather than anything that's required to compile & work.
- phaylon 9y agoYou can implement traits for both kinds of references, so a function might accept both.
- johncolanduoni 9y agoThe same thing can still happen with Deref coercions or type inference, so this is something the compiler already has to deal with.
- phaylon 9y agoI'm not sure what you mean. Can you give an example?
- johncolanduoni 9y agoIf struct A implements trait S and trait T, and there are two functions fn foo(x: &S); fn foo(y: &T); in scope, then the compiler needs you to disambiguate foo(&a) if a: A. This isn't a whole lot different than disambiguating the same expression if the functions are: fn foo(x: &A); fn foo(x: &mut A);
- saghm 9y agoWhen you use `&mut some_point`, (i.e. immutably borrow "some_point"), the compiler requires that "some_point" is not borrowed anywhere else at the same time. In order to enforce that invariant, it needs to know what borrows are mutable and what borrows are immutable. Perhaps what you're wondering is "why can't the compiler infer when a borrow needs to be mutable instead of having the programmer specify it"? I can't say for certain what the answer to this is, but my guess is that it would be rather expensive. Consider this example (reusing the "add" function from above): let mut foo = Point { x: 1, y: 2 }; let bar = &foo; add(&foo, &foo); ... // Much later reset(bar); Under the proposed "mutable reference inference" rules, should this compile? It's not necessarily trivial to determine this (especially if the "..." contains a lot of code), but it should not, since `bar` must a be mutable borrow to be able to pass into `reset`, which means that the borrows of `foo` to pass to `add` shouldn't be allowed. This means that the compiler would have to a rather expensive lookahead to determine whether any borrows are valid (and some people already complain that Rust compiles too slowly!). Forcing users to specify when borrows are mutable makes the compilers job much easier, not to mention the readability benefits that comes from making mutability explicit.
- kaoD 9y agomatch some_int { // Why not Some(& s) => ... ??? Some(ref s) => println!("{}",s), None => unreachable!() } To me his proposed syntax looks like the exact contrary use case: pattern-match on something that's a reference. EDIT: And that's actually the case. https://play.rust-lang.org/?gist=017740fbff2c5923dec3cc514bdba2f7&version=stable&backtrace=0 https://play.rust-lang.org/?gist=017740fbff2c5923dec3cc514bd... In that case it works because `w` is copy. For non-copy types, it prevents moving out of the borrow (as expected). https://play.rust-lang.org/?gist=241728575a382c54c324b0201c5f7ea5&version=stable&backtrace=0 https://play.rust-lang.org/?gist=241728575a382c54c324b0201c5...
- masklinn 9y ago> To me his proposed syntax looks like the exact contrary use case: pattern-match on something that's a reference. > EDIT: And that's actually the case. Yes, &s in a pattern is already valid and captures a "dereference" by value. While ref captures a value match by reference.
- sanderjd 9y ago> ref captures a value match by reference I share the OP's sense that this syntax is non-obvious. Could you parse what it means to "capture a value match by reference"?
- thristian 9y agoThis is all part of "destructuring pattern matching". Let's say we have: struct Something<T>(T); let x = 42; When we say: let y = Something(&x); ...we're constructing a value of a particular type on the right-hand-side of the `=`. Written out, it would be something like "take the value x, calculate its address in memory, and wrap that reference in Something()". let Something(&z) = y; ...does the reverse operation. Written out, it would be something like "take the value y, remove the Something() wrapper to get the value inside, dereference the pointer to find the value pointed to, then assign a copy of that value to the variable z". At the end, z == x. Note that the last step is copying a value: it only works if value is of a type that can be safely memcpy()'d (in Rust terminology, if the type implements the Copy trait). In this case, the value happens to be the integer 42, and integers are Copy, so it all works out. However, not all types are Copy, so if you can't copy the value, what happens? If the value you're destructuring owns the inner value, it's easy—you just become the new owner: let x = Something(123); // x owns this Something instance. let y = Something(x); // the value from x is moved into y. let Something(z) = y; // the value inside y is moved into z. On the other hand, if the value you're destructuring doesn't own the inner value, you can't just take ownership of it. If you want a handy name to use when talking about that value, it's going to need to be a pointer. let x = Something(42); // a value that is not Copy. let y = Something(&x); // a reference, so we can't take ownership //let Something(&z) = y; // Can't take ownership of x! let Something(ref z) = y; // z is a reference to x You can play with these examples in the Rust Playground: https://is.gd/LhrphO https://is.gd/LhrphO
- flavio81 9y agoTL;DR: He is happy. Better toolchain (environment manager, package manager, etc) Macros are easier to do with Rust (than in Scala). IDE: happy with MS Visual Studio Code Syntax is not too different. Type system: He likes it. Calls C code with a similar syntax than doing it with Scala Rust allows you more control on how to use your memory allocation. (Or forces you to take control). ------- My own take on Rust (haven't used the language yet, only looked at specs) is that it appears to be a more "sane", state-of-the-art version of C++, streamlined, without all the cruft and all the legacy. And with a much better object oriented system based on Traits.
- look_lookatme 9y agoDon't let the Rust fanboys catch you calling it OO.
- saghm 9y agoWhy? OO doesn't have to have inheritance.
- steveklabnik 9y agoThe second edition of the book has a chapter https://doc.rust-lang.org/nightly/book/second-edition/ch17-00-oop.html https://doc.rust-lang.org/nightly/book/second-edition/ch17-0... I personally don't feel Rust fits into either of the two big schools of OOP, but that doesn't mean that it's not OO. Part of the problem with OO is that the definitions are hard.
- saghm 9y agoI tend to think of OO as a spectrum rather than a binary thing, and I think that Rust certainly belongs somewhere on it. I'm not convinced there's any language that can be considered "completely object-oriented"; Java has primitives, and Python, while considering everything an object, is perfectly content to let you use functions and variables without writing a single class. JavaScript doesn't even have classes, but I think it still can be considered to be at least partially OO. I might just have a more liberal than average interpretation of what OO means, though.
- qsymmachus 9y ago> If you lean more towards the functional programming paradigm side of Scala then you’ll probably love the following about Rust’s type system Some of the more popular functional languages have advanced type systems, but not all functional languages are statically typed. Lisp, the granddaddy of all FP languages, is dynamically typed. So is Elixir. Can we stop conflating FP with static typing?
- dlwh 9y agoSure, but the FP side of Scala is definitely firmly in the advanced static type system camp.
- kod 9y agoProbably not, at least until people stop conflating "has first class functions" with "avoids or tracks mutation".
- swsieber 9y agoI don't think they were. Static type systems can greatly hinder FP. Or to be more specific, language semantics and runtime behavior (encoded by the type system) can influence how powerful your FP is - and by "powerful", I mean how much boiler plate you need and how much you can generalize things. So yes, having certain characteristics of type systems do help with FP while not being core to FP. It can certainly make things more pleasant though.
- kibwen 9y ago> Can we stop conflating FP with static typing? I'm all for that, but first we probably need to actually define what "functional programming" means, which seems to be one of the great unresolveable debates of computer science. :P To wit, I can think of no reasonable definition of functional programming that includes Lisp, Scheme, ML, and Haskell that does not also include every popular language aside from C. Even Java has first-class functions these days, sort of...
- lmkg 9y agoI tend to use the term "function-oriented" rather than "functional" since I think it fits the spirit of the phrase better. You're right that language features alone won't distinguish enough, and that means that functional programming is more about culture and standard practices. Once you embrace that, it's a bit easier to wrangle out a definition. The definition I have in my head is something along the lines of "functions are the tool that the language prefers to address common software design concerns." So for example, Encapsulation is a big deal when designing software. Object-oriented languages will address this concern with their object system (via accessibility modifiers), while function-oriented languages tend to address this with functions, via lexical scoping and closures. OOP languages use their object system to achieve runtime-dynamic behavior through inheritance or composition or interfaces, while function-oriented languages will more often try to pass first-class functions as arguments. And so forth. It's not a 100% thing. Common Lips will solve some of its problems with macros or its module system, and ML will solve some of its problems with types. But you do find the languages (or communities) prefer functions over their object systems, while e.g. Python or Ruby prefer the reverse. Although, I do find myself wondering if Haskell should actually be considered a type-oriented language. Again, it's not just about the features of the type system, it's the fact that the type system is the main tool that it tries to use to solve every problem about designing software.
- brson 9y ago> As with Swift, I haven’t been able to find conclusive evidence nor credit given to suggest that there was any influence from Scala on Rust … Scala was a significant influence on Rust. Niko did his dissertation in Scala, and several other team members were fans. Let it be known!
- corn_dog 9y agoThanks. I've done scala for a few years and only dabbled in rust but it certainly seemed plausible that various decisions in rust, about what to do and not to do, had been informed by scala. Glad to have it confirmed.
- kibwen 9y agoI took that to mean that Scala's influence on Rust is non-obvious. I might describe Scala's general design philosophy as "exuberant", whereas Rust has historically tried to have as little machinery as possible to achieve its safety goals. Scala's much more flexible, much less explicit, and much more TIMTOWTDI.
- doug1001 9y agothanks for doing this two questions: from the perspective of a Scala dev, does Rust have a solid collections library (including persistent structures such as Scala's Vector type) with mutable counterparts (eg, ListBuffer)? second, from a quick glance it seems Rust's top-level concurrency model (locks, thread ownership) is the polar opposite of Scala's (Actor-based)--is this correct? Scala's concurrency model (based on Erlang), along with trait-based multiple inheritance, were Scala's initial selling points for me
- logicfiction 9y agoIs actor-based really considered top level for Scala? Akka is still a framework and not in the language itself, I'd argue the core concurrency is Java-based thread pooling enriched with Scala's Future type. That said, I do agree that Akka is heavily favored as the ideal in Scala development as part of Lightbend's take on the Scala ecosystem, and maybe that was mostly your point.
- evacchi 9y agoActors (not akka's) used to ship as part of the standard library. I'd argue it's been the killer app that drove many people in, in Scala's early days. At some point Akka grew bigger and matured so the original actor library was spun off in its own library, basically deprecating it in favor of akka.
- amaranth 9y agoRust doesn't have a good persistent collections library, they're hard to do without a GC. On the other hand, the borrow checker makes it easier to deal with shared mutable data so there is less of a need for it.
- FreeFull 9y agoOn the other hand, just sticking a Vec<T> into Rc or Arc should work well enough for quite a few things.
- staticassertion 9y ago
- Others 9y agoConcerning cross compiling, one useful tool the author may of missed is [xargo](https://github.com/japaric/xargo https://github.com/japaric/xargo).
- knavely 9y agoIs there a Rust Console ? As a Scala/Haskell programmer, this is my biggest wish right now.
- corn_dog 9y agoHaving done scala for a few years and only dabbled in rust, I also get the impression that the rust community is trying to have good default answers to questions like how to do json, or which web framework to use. As opposed to scala-land where if you ask three scala devs those questions you'll get five answers.
- tekacs 9y agohttps://www.scala-lang.org/blog/2016/11/28/spp.html https://www.scala-lang.org/blog/2016/11/28/spp.html is probably what you would call the Scala folks' attempt at this. That said, it seems more common in Rust for there to be a consensus about which library to use. Perhaps this is simply due to its age? I've seen a few libraries recommend you migrate to others because the pace of Rust conceptual evolution made the first library outdated in short order This is somewhat less so now, though very recent features like custom derive and even impl Trait can affect dramatically the aesthetics of a library and how someone chooses to implement it.