4 ms·
> keywords like "let" are extra work for the programmer because the compiler can infer them automatically. Apparently, the "let" keyword is needed for parsing
by 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.
- charlieflowers 13y agoThis is very informative, thanks. If you could share some detail on how Rust falls short of Haskell type classes, I'd love to hear it. I'm digging into Rust, but haven't gotten that far yet.
- stream_fusion 13y agoThe Rust trait/typesystem doesn't currently support higher kinded polymorphism.
- adamnemecek 13y agoIt MIGHT in the near future though http://hydrocodedesign.com/2014/04/02/higher-kinded-types/ http://hydrocodedesign.com/2014/04/02/higher-kinded-types/
- dbaupp 13y agos/near//. That article is more "let's describe HKTs in Rust-y terms, so that more Rustaceans understand their purpose" than an actual proposal for implementing them in Rust. Any concrete proposal (i.e. covering implementation details and corner cases, rather than just some syntax/use-cases) would need to go through the RFC process: https://github.com/rust-lang/rfcs/blob/master/active/0001-rfc-process.md https://github.com/rust-lang/rfcs/blob/master/active/0001-rf... ((Unfortunately?) I imagine such an RFC would spend a month or two (or more) being discussed/thought-about.... And double that discussing syntax. ;P )
- jganetsk 13y agoThere are LOTS of things you can do with type classes, and they are super sexy. Let me try to enumerate them. One commenter already noted higher-kinded type classes, which is the basis of Haskell's libraries and do-notation for monads. That's HUGE. But besides higher-kinded type classes, there are others: * Factories and constants in your interfaces. Type classes allow you to put a type variable in the return argument of a method. This lets you put factories and constants in your type classes. For example, if you look at the monoid type class: class Monoid a where mappend: a -> a -> a mzero: a And I can implement this instance Monoid String where mappend x y = AppendStrings x y mzero = "" instance Monoid Integer where mappend x y = x + y mzero = 0 So now I can write generic code where I can get the "zero constant" of a type without knowing what type that is. I could just have a genericMonoid : T where T is a type variable, and all I know about it is that it belongs to class Monoid (maybe it's an integer, maybe it's a string, maybe it's something else). I can now get the zero value of whatever that type is. In fact, my method doesn't have to be dispatched on the type of some input argument. I can dispatch on the return value. Something like multiplyByZero : [Monoid T] int -> T multiplyByZero 0 = mzero multiplyByZero n = mplus mzero (multiplyByZero (n-1)) (Forgive my crappy Haskell pseudo syntax, I haven't written it in a while). Not that multiply by zero is useful. * Operators of greater arity than 1. So I hinted at this above. With "method syntax" arity is always 1. I have one "this". I can operate on it. For all other arguments to my method, I cannot actually constrain them to have the same type as "this". At best, with OOP, my method belongs to some base class, and I can insist that the non-this arguments are of base class type, but I cannot constrain them to have "this" type. This is illustrated in the monoid example above, or in the Eq example I gave, where it can be guaranteed that the two operands to == are always of the same type. In Java, the equals method has to take one argument of type "Object", while the "this" argument has the class's type. * Multiparameter type classes. (This is behind a flag in the Haskell compiler). You can do something like this: class Eating E F where eat :: E -> F -> Meal # E is short for Eater # F is short for Food so now I can type check to make sure that eaters eat the right type of food. instance Carnivore Meat where eat = # specialized implementation for eating meat instance Carnivore Vegetables where eat = # specialized implementation for eating vegetables instance Vegeterian Vegetables where eat = # specialized implementation for vegeterian food