3 ms·
A good test for me is whether the simplest possible implementation works: template <typename Range> void draw_all (Range r) { for (e : r) { draw
by nly 13y ago
A good test for me is whether the simplest possible implementation works:
template <typename Range>
void draw_all (Range r) {
for (e : r) { draw(e); }
}
There are no magical symbols here at all. It's not efficient, but it's completely memory safe... breaks with unique_ptr though, which is a good indication for me that unique_ptr is the wrong choice. Here's the less safe 'borrowing' version:
template <typename Range>
void draw_all (Range const& r) {
for (e const& : r) { draw(e); }
}
and a sane compromise:
template <typename Range>
void draw_all (Range r) {
for (e const& : r) { draw(e); }
}
How would you write all of these in Rust?
- pcwalton 13y agoThe reason for use of a unique pointer is that if the caller had an array of unique pointers to Drawables, then you want the caller to be able to call draw_all() without recreating the array. You could come up with a generic function that doesn't require unique ownership (for example, one that takes an Iterator<&Drawable>), but the function in that example wasn't generic because making everything generic just in case is overengineering. You seem pretty confused about how borrowing works. Borrowing is tangential to that function.
- nly 13y agoI'm not confused about borrowing, what I'm saying is is that borrowing, plain refs in C++, are a performance hack in both languages. In my final version above, I have assumed copying a range is cheap, and copying a Drawable is expensive and unnecessary... seems perfect to me, and it will not compile passing a vector of unique_ptr's, which is what I would want
- pcwalton 13y agoReferences are not a performance hack; they're a fundamental way to avoid lots of moves, which obscure algorithms and cause a lot of mutation. (In Rust they are memory safe.) Anyway, if you wanted a generic reference-taking version: fn draw_all<I:Iterator<&Drawable>>(iterator: I) { for drawable in iterator { drawable.draw(); } } And a generic move version: fn draw_all<I:Iterator<Drawable>>(iterator: I) { for drawable in iterator { drawable.draw(); } }
- nly 13y agoInternal moves make no sense. You can't move an object out of a container that has invariants, like an ordered map. This is why it only makes sense for the caller to move a container in to the function. This is efficient. I've edited my 3rd version appropriately, as moving from a const& was clearly bogus. template <typename Range> void draw_all (Range r) { for (e const& : r) { draw(e); } }
- pcwalton 13y agoUsing the container's iterator to move allows the container to enforce those invariants.
- nly 13y agoI've totally lost track of what we are arguing about. Anything containing a unique element has to be logically unique itself, right? In C++, which defaults to value semantics, it's required that you move your container if it contains a non-copyable (unique) element. So you only need to move into the draw_all function in this case, which is why taking the range by value is not just efficient, but semantically correct. If the caller moves in to the function, then when it returns the caller will no longer own any elements. The callers vector will be empty, and the elements themselves will still be unique having never been copied, moved or "borrowed". If borrowing isn't a performance hack, then why not make everything you're ever likely to borrow shared? I'd argue anything you're drawing is shared between the draw routine and the caller. Drawing a distinction just because the caller is suspended, seems like an impediment to future change if, for example, you later switch to a coroutine or an asynchronous/threaded operation. Copying the range and sharing elements gets you this for free. In summary, 'draw_all' as specified was a bad API because: * It restricted the type of range/container passed to it * It had unnatural ownership semantics (borrowing a box of unique things without saying you're borrowing those things is weird). * The implementation, as was, required further borrows which were only implied. In C++ you take everything straight away.
- pcwalton 13y ago