6 ms·
A few observations knowing nothing about Rust until I read this, perhaps someone can shed some light on these: THE BAD: * keywords like "let" are extra work f
by zackmorris 13y ago
A few observations knowing nothing about Rust until I read this, perhaps someone can shed some light on these:
THE BAD:
* keywords like "let" are extra work for the programmer because the compiler can infer them automatically.
* I’m not a fan of printf()-style parameters, because they break ordering (remarkably, php does this best with its “.” and “$” notations).
* Mandatory curly brackets are also more work, although they (could) prevent subtle bugs later.
* I’m not a fan of the generics syntax, borrowed from templates in c++ (I prefer the something like the “auto” style more, shifting work to the compiler).
* Forcing the programmer to manually place values on the heap with “~” is annoying, when other languages can automatically optimize such things.
* It seems that move semantics distinguish between objects and primitives, instead of everything being an object?
* Borrow is confusing to me, because I would probably make an immutable object passed to a function as mutable create a copy (but maybe it avoids subtle bugs).
* Lifetimes completely confuse me, as it seems like they would be handled by garbage collection?
* I don’t have a problem with the “static” keyword, except that wouldn’t global immutable variables be static anyway?
* I might have gone with traits (interfaces) everywhere instead of distinguishing them from structs, because that seems to be the direction the world is going, to avoid inheritance problems.
* I’m not sure I would have distinguished closures from functions with special syntax (although maybe it prevents subtle bugs later).
* #[bench] seems a little weird (putting profiling calls in the code), although I do find myself wanting this constantly, so if they conditionally compile they could be handy.
THE GOOD:
* Real preemptive threads it seems (as opposed to Go’s goroutines which are cooperative threads/coroutines).
* UTF-8, non null-terminated strings are good (I wonder how they handle character boundaries though, especially in languages besides English).
* Some argument could be made that borrowing c++ concepts makes Rust more approachable.
- kam 13y ago> I’m not a fan of printf()-style parameters, because they break ordering (remarkably, php does this best with its “.” and “$” notations). Rust's println! macro also supports named format parameters, which don't need to be in the same order as the format string. println!("{foo}: {bar}", bar=5, foo="abc"); > It seems that move semantics distinguish between objects and primitives, instead of everything being an object? Structs fulfilling the Copy trait (meaning they don't have destructors and can be duplicated with a simple memcopy) have the same copy semantics as primitive number types. > Forcing the programmer to manually place values on the heap with “~” is annoying, when other languages can automatically optimize such things. > Lifetimes completely confuse me, as it seems like they would be handled by garbage collection? Rust is a systems language. The language makes memory allocation and management explicit, by design. Lifetimes allow Rust to provide memory safety at zero run-time cost. They're how the programmer proves to the compiler that pointers are valid at the time of use. There is no garbage collection by default; only when you use the Rc or Gc types from the library.
- xiaq 13y ago> * Lifetimes completely confuse me, as it seems like they would be handled by garbage collection? Interesting misconception :) Lifetime is used to avoid garbage collection. By expressing the lifetime of a variable preciously in the code, the compiler knows statically (i.e. when compiling instead of in runtime) when an object can be destroyed, avoiding GC overhead and offering deterministic destructure.
- masklinn 13y ago> keywords like "let" are extra work for the programmer because the compiler can infer them automatically. I strongly disagree. In my experience scope inference either completely blows (javascript, coffeescript), sucks (python) or requires language asymmetries (ruby). Either way, I've come to see it as an awful idea. > I’m not a fan of printf()-style parameters, because they break ordering (remarkably, php does this best with its “.” and “$” notations). Rust does not use printf-style parameters, it uses C#-style parameters[0] (also used in Python's "new" str.format[1][2]). The format placeholders can explicitly specify the parameter index (in whatever order, e.g. "{1} {3} {0} {2}") or name ("{foo} {bar} {baz}"). See third and fourth println! of the second page. > I’m not a fan of the generics syntax, borrowed from templates in c++ (I prefer the something like the “auto” style more, shifting work to the compiler). ? Rust already has local type inference (which is what `auto` does), generics syntax is orthogonal to TI. > Forcing the programmer to manually place values on the heap with “~” is annoying, when other languages can automatically optimize such things. The whole point there is to have the developer knowingly decide whether they want allocation on the stack, the heap or somewhere else (managed or refcounted heaps are/will be available[3]). Rust is aiming at C++'s niche, not Java, having the language heap-allocate by default and try to guess if it could just stack-allocate wouldn't work. > Lifetimes completely confuse me, as it seems like they would be handled by garbage collection? Rust's GC is optional, and most of the standard library (and as experience shows libraries and code bases) can avoid GC. The point of lifetime is to avoid both mandatory GC and manual memory management (à la C): if the compiler knows when an object completely stops being used, it can automatically and safely release even heap-allocated objects. > I might have gone with traits (interfaces) everywhere instead of distinguishing them from structs, because that seems to be the direction the world is going, to avoid inheritance problems. Where do data members (attributes) live if you don't have structs? > #[bench] seems a little weird (putting profiling calls in the code), although I do find myself wanting this constantly, so if they conditionally compile they could be handy. #[bench] is a specialisation (of sorts) of #[test], it's only compiled in when using --test, and run when passing —bench to the resulting test harness. see http://static.rust-lang.org/doc/0.10/guide-testing.html#microbenchmarking http://static.rust-lang.org/doc/0.10/guide-testing.html#micr... Note that it's not a profiling call, it doesn't profile anything (unless you enable the profiler while running the bench), it only provides coarse/toplevel timing information, like python's timeit or ruby's Benchmark [0] http://msdn.microsoft.com/en-us/library/system.string.format.aspx#FormatItem http://msdn.microsoft.com/en-us/library/system.string.format... [1] https://docs.python.org/3/library/string.html#formatstrings https://docs.python.org/3/library/string.html#formatstrings [2] technically it's probably Python's version, IIRC C# requires the index and has no support for name-based parameters [3] and maybe user-provided boxes, such as a zeroing box (to store cryptographic keys) or a GPU box, or local v remote for clusters, the sky's the limit
- ben0x539 13y agoThanks for taking the time to write out your observations! :) Some comments (speaking as someone who has followed rust for a while, but isn't heavily involved): In general, rust wants to enable people to write code with unsurprising performance. So leaving it to the compiler to decide whether a value lives on the ~ heap wouldn't be appropriate for all use cases. Similarly, the thing about the complicated lifetimes and borrowing shenanigans is that they exist to enable safe, efficient code that doesn't require a garbage collector. Move semantics distinguish copyable-by-memcpy types, those can be primitives or user-defined types. It just can't be something like a ~ pointer or a type with a custom destructor or whatever, since having duplicates show up arbitrarily would mess up the associated bookkeeping. Primitive types also tend to implement the Clone trait so they can be explicitly copied with the .clone() method just like user-defined types implementing it. Syntax is always a matter of taste... I think most people are happy with "let", there's always people who'd rather have s-expressions or significant-whitspace-delimited blocks instead of curly braces but also the other way around, I sympathize with distaste for the generics syntax to some extent... in the end you can't please everybody and you could probably do worse than making your language that is aimed at C++'s niche look a bit like C++.
- aaronblohowiak 13y ago> Real preemptive threads it seems (as opposed to Go’s goroutines which are cooperative threads/coroutines). Go's goroutines are preemptive.
- masklinn 13y ago> Go's goroutines are preemptive. Not quite. Since 1.2, goroutines can yield on non-inlined function calls (in 1.1 it happens only on IO or on an explicit Gosched() call). A goroutine with no funcall or only inlined function calls will still block the scheduler (and the whole system if there's only one scheduler).
- aaronblohowiak 13y agoThis tends not to be an issue in practice as you almost always have non-inlined function calls or short-executing loops. The one way in which this might be a problem is if you try and implement a spinlock, but there is no excuse for that given the go concurrency features.
- masklinn 13y ago> This tends not to be an issue in practice as you almost always have non-inlined function calls or short-executing loops. Until it's an issue because somebody set up CPU-bound image processing, and then by lying you've set people up for failure and you've given them a huge blind spot once things start not working correctly.
- aaronblohowiak 13y agoGood point. It was a shortcut that I shouldn't have taken.
- burntsushi 13y agoYou're right, it's generally not an issue. Acting like Goroutines are preemptive isn't likely to land you in a lot of trouble. Nevertheless, they aren't preemptive, so you shouldn't say that they are. Maybe "almost preemptive"?
- jganetsk 13y ago> keywords like "let" are extra work for the programmer because the compiler can infer them automatically. Apparently, the "let" keyword is needed for parsing so that Rust can have non-trivial patterns on the left hand side of the assignment operator. As in, destructuring binds. I'm assuming you don't know about pattern matching. Pattern matching is a good thing. Maybe even one of the greatest things. > I’m not a fan of the generics syntax, borrowed from templates in c++ (I prefer the something like the “auto” style more, shifting work to the compiler). I'm assuming this means you are not a fan of generics. Parametric polymorphism is one of the most powerful things in all of type systems. http://en.wikipedia.org/wiki/Parametricity http://en.wikipedia.org/wiki/Parametricity It's deeply regrettable that not every programming language that exists today has great parametric polymorphism support (Go, I'm looking in your direction). Type parameters and type variable are CRUCIAL for this. > Forcing the programmer to manually place values on the heap with “~” is annoying, when other languages can automatically optimize such things. The point of the "~" is to make ownership explicit. C++'s type system is insufficient to make guarantees about ownership. It only recently gained move semantics, which enabled unique pointers, but it makes no guarantees about any other references you make to the object (with the use of .get() and non-smart pointers). As a result, you have to make sure you use C++ smart pointers with discipline. Anytime you are required to do something with discipline, that's an opportunity for a type system to jump in and check your discipline. > It seems that move semantics distinguish between objects and primitives, instead of everything being an object? Afaik, in Rust, everything is a primitive, and nothing is an object. What does object even mean? > Borrow is confusing to me, because I would probably make an immutable object passed to a function as mutable create a copy (but maybe it avoids subtle bugs). You've never passed anything by reference before? Note that borrowing has less to do with mutability and more to do with ownership discipline. > Lifetimes completely confuse me, as it seems like they would be handled by garbage collection? Have you imagined the possibility of a language that does no garbage collection, because it has worked it all out statically? A garbage collector is nothing more than a potentially compile-time problem deferred to run-time. When we moved type-checking from the run-time world to the compile-time world, we gained HUGE benefits in time/space performance and in informing the programmer upfront more about the potential behavior of their program. If only we could do the same thing for memory management. That's what Rust is trynig to achieve. > I might have gone with traits (interfaces) everywhere instead of distinguishing them from structs, because that seems to be the direction the world is going, to avoid inheritance problems. Inheritance does cause lots of problems. It's not only that, but Haskell type classes are general and cover lots of real world cases that shitty mainstream type systems can't. One simple example I give is a type-safe .equals method. Java's .equals is type checked ad hoc by the programmer (the argument is an Object, and the programmer typically uses "instanceof" and a cast). You can use generics, but it's still broken, and if someone cares I can go into more detail. Haskell type classes is the only type system I know of where you can just say "hey there's this class of types called 'Eq', where there's a method that takes two arguments of type 'self' and returns a bool". So many OOP type systems only let you have 'this' of type 'self' in a method. I'm handwaving a bit in my description here, but it's a shame this problem isn't just obvious to everyone. Rust is inspired by Haskell type classes, but not fully there.
- shmerl 13y ago> Lifetimes completely confuse me, as it seems like they would be handled by garbage collection? RAII approach (https://en.wikipedia.org/wiki/RAII https://en.wikipedia.org/wiki/RAII) is more efficeint than garbage collection and also way better because of predictability. I'd say it's also more straightforward. Instead of some unpredictable and chaotic background magic going on, resources are deallocated right when they aren't needed anymore. I really don't see why would anyone prefer GC to it.