4 ms·
> * The borrow checker. Memory safety is great but there are plenty of memory-safe designs that the borrow checker complains about. With modern C++ and sound de
by epage 4y ago
> * The borrow checker. Memory safety is great but there are plenty of memory-safe designs that the borrow checker complains about. With modern C++ and sound design techniques, memory safety is not something I worry about much in my C++ projects, and the static analyzer proves me out on that. In particular, a typical application design involves some variation of the Observer pattern (implementations can vary wildly),
Don't you still have Iterator invalidation to keep track of and I thought I saw posts regretting using `std::string_view` because it is easy to reference deleted memory.
Anecdote: I maintain a template language library for Rust. I saw the potential for it to speed up if I used more borrowed data (like `&str`, Rust's version of `std::string_view`). I gave it a try and once it compiled, it just worked without crashes. I reflected back on if this was in C++ and the conclusion I came to was that maintainable code is a much higher priority than performance and that any future change in a similar C++ code base would require global analysis to make sure it was safe, making the performance gains not worth the lack of maintainability. In Rust, its been trivial.
> Where are the functors??!?!? Lambdas are great and all, but I can’t create unbound trait/struct functors and then invoke them on strict references later? This is a huge limitation in runtime flexibility for application development. Boost::function and boost::bind worked with VC6 twenty years ago! When I realized Rust didn’t have functors, I became much less interested in the language.
The fact that you can't `impl Fn` bothered me for a long time. It was one of the many examples of where Rust felt unfinished.
Recently, I've seen an inverted pattern for this, define a trait and `impl MyTrait for Fn`. I've found I much prefer this pattern over the cases where I would have used a functor. Granted, there are more ad-hoc cases where functors would be better.
> No function overloading. This is just silly and onerous. Rust could create sensible restrictions to avoid ambiguities and C++’s ADL/type coercion complexity but there’s nothing ambiguous about having two functions with different arity sharing the same name. The hardest problem in CS is naming things, and Rust’s lack of function overloading makes it harder still.
I thought I'd miss this but it hasn't been as bad as I expected.
- jonstewart 4y ago> Don't you still have Iterator invalidation to keep track of and I thought I saw posts regretting using `std::string_view` because it is easy to reference deleted memory. I don't run into iterator invalidation that much in practice. I'm not often erasing elements from collections. There are all sorts of things like std::string_view where you could reference bad memory if you don't manage lifetimes, but I... just... come up with designs where lifetimes are managed properly? It's probably habit and very intuitive for me, hardly conscious of it. Also, this is where the static analyzer is fantastic. If you have good unit test coverage, it will let you know you've screwed up and pinpoint where. I don't mind the borrow checker as a default, but it would be nice for an escape hatch that wasn't an unsafe{} block. Perhaps one with runtime checks? Predictable conditions are virtually free with branch prediction. > Recently, I've seen an inverted pattern for this, define a trait and `impl MyTrait for Fn`. I've found I much prefer this pattern over the cases where I would have used a functor. Granted, there are more ad-hoc cases where functors would be better. Can you give an example or provide a citation to this? I'd love to read more.
- Rusky 4y ago> Can you give an example or provide a citation to this? It would look something like this: trait MyOperation { fn run(&mut self, a: T, b: U, c: V) -> R; } impl<F> MyOperation for F where F: FnMut(T, U, V) -> R { fn run(&mut self, a: T, b: U, c: V) -> R { self(a, b, c) } } fn my_api<F: MyOperation>(f: F) { ... f.run(a, b, c) ... } struct MyFunctor { ... } impl MyOperation for MyFunctor { ... } my_api(MyFunctor { ... }); That is, when defining `my_api` and you would like to accept both lambdas and functors, instead of using a `Fn` trait bound directly, use a more specific trait which has a blanket impl for lambdas.
- epage 4y ago> Can you give an example or provide a citation to this? I'd love to read more. `nom`'s `Parser` trait is implemented for functions. You can mix and match parser functions and parser types, including the built-in types that are returned when you call the post-fix combinators on the `Parser` trait. https://docs.rs/nom/latest/nom/ https://docs.rs/nom/latest/nom/