>> What would be Rust alternative, except passing the errors all around >> parser? Doesn't it grow parser code by at least 50%?
> Haskell has no exception, it has Monads instead. Rust reuses Option and > Result (Maybe and Either in Haskell) with great success. For all my love to Haskell, I wouldn't consider it an example of a practical programming language. I mean, there aren't many projects written in Haskell of a size larger than GHC. >> Why not make a trait BaseString and apply all the operations to it? > It's "heavier", in several ways. > [...] > In short, Slice is just a superior alternative. I agree that it is conceptually simpler. But compare code, required to read one integer from a file in Python, C++ and Rust: a = int(f.read_line()) int a; f >> a; let a: int = from_str(f.read_line().unwrap().as_slice().trim()).unwrap(); Two unwrap's are caused by the lack of exceptions, as_slice is to convert String -> str, trim is because from_str doesn't skip whitespace (I suppose, the last one is non-essential). On Thu, May 29, 2014 at 1:27 PM, Matthieu Monrocq <[email protected]> wrote: > > > > On Thu, May 29, 2014 at 8:32 PM, Oleg Eterevsky <[email protected]> wrote: >> >> > Overloaded functions are unnecessary in Rust. In C++ they are heavily >> > used because of templates use duck-typing, but Rust uses explicit >> > interfaces >> > (typeclass-like) which makes it unnecessary. >> >> I agree. This is one of the main use cases for overloaded functions in >> C++, and it is irrelevant to Rust. Considering this, I'd say, that >> introducing optional named parameters might be a better solution. >> >> > let mut result = vec!(); // type of "result" is "Vec<...>" which is only >> > partially known >> >> > for x in some_range.iter() { >> > doit(result, *x); // there is a single "doit" in scope, whose >> > arguments is "Vec<int>" thus this is the type of "result" >> > } >> >> To be honest, such a non-local type inference creeps me out. It might >> be an unending source of errors. While it probably can be used in some >> quick and dirty code snippets, I would never commit such code to a >> repository of any self-respecting project. > > > Well, I don't find it creepy. I suppose it's just a matter of habit. It > seems a bit weird, at first, but even after just a couple of days it just > feels normal again (at least it did for me). > > Remark that it is local: local to the method. It's not at the level of > Haskell where the very signature of a method can be inferred (which gives > rise to infuriating error messages). > > All in all, it seems to strike a good balance. > >> >> >> > Named parameters and default values are just syntactic sugar; they might >> > feel handy, indeed, but in general they can be emulated => Rust already has >> > named parameters for "struct" initialization, and it also has functional >> > update for those "struct" thus as long as you are willing to add one more >> > symbol: >> >> While it is indeed a syntactic sugar, I believe that it's an important >> one. (You may argue that all of Python is just syntactic sugar over >> some dictionary operations and all of Haskell is syntactic sugar over >> linked lists.) I can't imagine anyone adding an explicit struct type >> for each and every function that might be expanded in future, or just >> has optional arguments. >> >> >> 2. What about exceptions? >> > As mentioned, the issue is about recovery: you modified a lot of stuff >> > in your "try" that is still accessible afterward, is it in a safe state ? >> > is >> > it in sensible state ? No one knows, and your compiler does not care. Tasks >> > enforce explicitness about what is modified, which helps in auditing. >> >> Just to make sure I understand how it all should work. Suppose I have >> a parser, that raises ParseError. I might use it like this: >> >> let something = Something::new(); >> >> try { >> something.parse_str("..."); >> } catch ParseError as e { >> println!("ParseError at {}:{}", e.row, e.col); >> return; >> } >> >> What would be Rust alternative, except passing the errors all around >> parser? Doesn't it grow parser code by at least 50%? > > > Haskell has no exception, it has Monads instead. Rust reuses Option and > Result (Maybe and Either in Haskell) with great success. > >> >> >> >> 3. It seems like almost any string operation requires as_slice(). >> >> Can't various string methods be also implemented for String? >> >> > It's the lowest common denominator approach, a slice is cheap (no memory >> > allocation) and universal (may refer either to heap allocated buffer or >> > static buffer). >> >> Why not make a trait BaseString and apply all the operations to it? > > > It's "heavier", in several ways. > > For the client: the Slice is written once, with all its operations, so where > in an "interface" setting you would need to provide a lot of operations > yourself, here you just provide ONE operation => the conversion so slice. > > For the user: a Slice is a concrete type, all functions/methods are thus > statically known and can be inlined at call site. The alternative with a > trait is either: > - paying a virtual call at each method, which is slower and inhibit constant > propagation a whole host of potential optimizations > - mono-morphizing the code for each implementer of BaseString, which is > generally referred to a "template bloat" in C++ (much more code is > generated, thus bigger binaries, clogging of the CPU caches, ...) > > In short, Slice is just a superior alternative. > > > >> >> >> On Thu, May 29, 2014 at 3:44 AM, Matthieu Monrocq >> <[email protected]> wrote: >> > >> > >> > >> > On Thu, May 29, 2014 at 2:38 AM, Oleg Eterevsky <[email protected]> >> > wrote: >> >> >> >> Hi! >> >> >> >> I've just recently started learning Rust. Here's a few questions that >> >> I have after writing about a hundred lines of code: >> >> >> >> 1. Why neither overloaded function, nor default values for arguments >> >> are supported? In statically typed language overloaded functions are >> >> quite safe and convenient. Also, from what I see, overloaded functions >> >> could be used in various use cases instead of macros. >> >> >> > >> > Overloaded functions are unnecessary in Rust. In C++ they are heavily >> > used >> > because of templates use duck-typing, but Rust uses explicit interfaces >> > (typeclass-like) which makes it unnecessary. >> > >> > Overloaded functions also don't play too well with type inference. C++ >> > works >> > around this by having a very simple type inference (ie, it only deduces >> > the >> > return type), but Rust's type inference is more advanced: >> > >> > let mut result = vec!(); // type of "result" is "Vec<...>" which is >> > only >> > partially known >> > >> > for x in some_range.iter() { >> > doit(result, *x); // there is a single "doit" in scope, whose >> > arguments is "Vec<int>" thus this is the type of "result" >> > } >> > >> > If you were able to overload "doit" though, suddenly things would get >> > hairy, >> > and you would have to start explicitly typing your variables left and >> > right. >> > >> > Thus, even besides the confusion a human being experiences (which >> > overload >> > is called ?) and the over-abuse of overloads in the first place (Qt >> > seems to >> > be doing nicely in that domain), there is a technical trade-off: better >> > type >> > inference or function overload ? >> > >> > Note: it also means that compiler error-messages are generally more >> > explicit; tried forgetting to implementation "operator<<" for a type and >> > see >> > what your favorite C++ compiler reports ;) >> > >> > >> > Named parameters and default values are just syntactic sugar; they might >> > feel handy, indeed, but in general they can be emulated => Rust already >> > has >> > named parameters for "struct" initialization, and it also has functional >> > update for those "struct" thus as long as you are willing to add one >> > more >> > symbol: >> > >> > struct FooArgs { x: int, y: int, } >> > >> > static DEFAULT_FOO: FooArgs = FooArgs { x: 4, y: 5, } >> > >> > fn foo(args: FooArgs) {} >> > >> > fn main() { >> > foo(FooArgs{x: 3, .. DEFAULT_FOO}) >> > } >> > >> > I personally feel that the name "FooArgs" could be inferred in the call >> > (since there is no overload of "foo" there is no ambiguity) which would >> > assist in this pattern. There is also the issue that static cannot be >> > build >> > from arbitrary functions, but that may be lifted with Compilation Time >> > Function Evaluation one day, and in the mean time you can have a >> > FooArgs::default() that computes the defaults. >> > >> > >> >> >> >> 2. What about exceptions? Is it a design decision not to support them, >> >> or are they planned for some future version? I understand, that >> >> exceptions make memory management more difficult, but maybe there are >> >> ways to restrict their usage to avoid problems (like making it >> >> impossible to pass references as exception parameters?) >> >> >> > As mentioned, the issue is about recovery: you modified a lot of stuff >> > in >> > your "try" that is still accessible afterward, is it in a safe state ? >> > is it >> > in sensible state ? No one knows, and your compiler does not care. Tasks >> > enforce explicitness about what is modified, which helps in auditing. >> > >> >> >> >> 3. It seems like almost any string operation requires as_slice(). >> >> Can't various string methods be also implemented for String? >> >> >> > It's the lowest common denominator approach, a slice is cheap (no memory >> > allocation) and universal (may refer either to heap allocated buffer or >> > static buffer). >> > >> > Rust is working on DST (Dynamically Sized Types) and once this lands >> > String >> > will be able to implement Deref, which will ease things. There is also >> > been >> > talk about auto-deref to make it even painless. >> > >> >> >> >> 4. It looks like vectors can be concatenated with + operations, but >> >> strings can't. Is it deliberate? >> > >> > >> > As someone else answered, it could be done. >> > >> > Personally though I've never liked that. '+' is mathematically a >> > commutative >> > operation and catenation is not a commutative operation. Also, in the >> > absence of overloading this means to catenate you have to implement the >> > Add >> > trait. This is just weird. I would prefer infix function calls (`cat`) >> > for >> > example, or another dedicated operator (++ ?) >> > >> >> >> >> >> >> 5. Simple indexing doesn't work for vectors: >> >> let a = vec![1, 2, 3]; >> >> println!("{}", a[0]); >> >> It's a bit surprising... >> >> >> > Could probably be implemented, may have to wait for DST though. >> > >> >> >> >> 6. impl ToStr for custom struct fails: >> >> error: conflicting implementations for trait `std::to_str::ToStr` >> >> note: conflicting implementation in crate `std` >> >> Is it a bug? Is Show implicitly assumed for all struct's? >> >> >> >> 7. The usage of mut is a bit confusing. It is supposed to be used as a >> >> qualifier for a variable, but it quickly becomes a part of the type, >> >> when you define functions like >> >> fn test(something: &mut Something) >> >> Maybe it makes sense move mut their? Or distinguish mut as a variable >> >> qualifier vs const as a propery of type? >> >> >> > >> > There has been an extended discussion about it, and I am also slightly >> > confused by the use of `mut`. It reads "mutability" but in the case of >> > references also implies exclusivity... >> > >> > At the end of the day though, there's already a number of high-profiles >> > changes going on (such as the above DST), so for now it's wait and see. >> > Once >> > those are implemented and the core developers have a bit more time on >> > their >> > plate, the issue might be revisited. >> > >> >> >> >> -- >> >> Thanks, >> >> Oleg Eterevsky. >> >> _______________________________________________ >> >> Rust-dev mailing list >> >> [email protected] >> >> https://mail.mozilla.org/listinfo/rust-dev >> > >> > > > _______________________________________________ Rust-dev mailing list [email protected] https://mail.mozilla.org/listinfo/rust-dev
