Hacker News new | past | comments | ask | show | jobs | submit | fluffything's comments login

Does that warn if you do:

vector<int> v{1,2,3,4}; for (auto&& i: v) v.push_back(i);


In my little toy build I set up no, it doesn't warn me that smashing a data structure while iterating over it at the same time gives UB. I'm 50/50 on if there's a flag for that, don't actually know.

Again, in the spirit of becoming more educated, what's the equivalent Rust code? I have `rustc`/`rustup` on my box so I can run that too.


This should be about equivalent. It does not compile, as expected. https://play.rust-lang.org/?version=stable&mode=debug&editio...


Another fun feature of rustc is you can execute this command: rustc --explain E0502

It gives you a small example of what the error is, explains it, and tells you how to fix it.

This is a very nice feature for new people (like myself).


That's definitely useful, and I'm not completely mystified by the notion that simultaneous borrowing of something as both mutable and immutable is something that Rust watches for.

In C++ you (usually) do this with `const`, which is fairly low friction, very statically analyzable, and I'm a little unclear what bug hardcore borrow-checker static stops me or my colleagues from making that `const` can't catch?


> In C++ you (usually) do this with `const`, which is fairly low friction, very statically analyzable, and I'm a little unclear what bug hardcore borrow-checker static stops me or my colleagues from making that `const` can't catch?

Rust catches the bug where you forget to use `const`, because the C++ compiler doesn't force you to use it. Your argument boils down to "I don't need Rust's borrow checker, I just have to remember to use a dozen tricks, follow various patterns and guidelines, run several static analyzers and runtime checks, and I'm almost there".

This is not meant as an attack against you. I used to program in C++, and I'm mostly using Rust now. The borrow checker frees up those brain resources which had to keep all those tricks, patterns, and guidelines in mind.


Here’s a simple point for you to consider. Let’s assume you and your team are 10x devs with perfect knowledge and use all the right tools. What happens when you guys leave or someone who isn’t up to your standard contributes, or is a junior dev?

Imagine all the best tools and all the best and memory safe features all rolled up to one called Rust.

Lastly, with C++ there are many situations where the memory unsafe actions are not caught until they are triggered dynamically. So like others have mentioned, you’d have to have the best test suite combined with the best of fuzzing etc.

Or you can just use Rust where nearly everything is caught at compile time with useful errors and when something funky does happen at runtime it will just panic before you cross into UB/ unsafe land


It is a pity that none of the replies to this comment has actually clarified your doubts...

Assuming the absence of `const_cast`, C++'s `const` ensures that a data of the particular type doesn't get modified. That's it. Rust on the other hand, tracks and restricts aliasing. This means you can't get both a constant and non-constant reference to an object or anything owned by it (more precisely anything transitively reachable from it). This is helpful in preventing issues with invalid pointers (like iterator invalidation). For instance, if you have already borrowed an iterator (which is essentially a reference) you can't issue mutable operations on the vector (issuing mutable operations entails a mutable borrow), and that could potentially reallocate the backing memory of the vector. This property is also useful for preventing multiple writes to the same location in a multi-threaded application, making programs adhere to the single-writer principle by-construction.

The downsides are that you might sometimes need to contort your code or your program architecture or perform mental/type gymnastics to write some trivially safe code or even use unsafe (or else lose performance) to satisfy the borrow checker.


I've read your comments in this thread, and with all due respect, I think what you're missing is a very simple truth: Rust is safe by default. C++ is not. This is Rust's core value proposition. It is precisely the thing that permits AND encourages designing safe abstractions even if it internally uses `unsafe`. Rust's value is in allowing you to compose safe code without fear.

If this doesn't seem like a game changer to you, then you probably don't grok its scope and impact. I don't really know how to give that to you either in HN comments. I think you maybe need a synchronous dialogue with someone.


Const in C++ is entirely unlike immutability in Rust. Even ignoring the existence of “const_cast”, C++ const is vastly weaker than Rust immutability in several extremely fundamental ways that should be immediately obvious if you’re a C++ professional with even a surface level introductory understanding of Rust. At this point you honestly should just take some time to learn a bit of Rust, and it will likely all make sense. If not, it’s possible you’re relatively amateur C++ developer (no shame in that) lacking experience of the many ways in which C++ const falls very short.

But to directly answer your question, suppose I have a “struct FloatArrayView { float* data; size_t size; };” Of course this is a toy example, but humor me for now and consider the following:

1. Without excessive custom effort (e.g. without manually unwrapping/wrapping the internal pointer), how do I pass it to a function in C++ such that the data pointer within becomes const from the perspective of that function (i.e. you’ll get a compile error if you try to write to it?) Hint: It’s very complex to do this in C++, vs trivially easy in Rust. And no, passing as const or const reference to “FloatArrayView” does NOT work. The only solution in C++ for complex composite types operating with “by-reference semantics” is ugly, complex, and horribly error prone to maintain correctly. For a more concrete example, consider how “unique_ptr” works with the constness of the value it holds. A “const unique_ptr<T>” is NOT a unique pointer to const T. A “unique_ptr<const T>” is, but the relationship and safe conversions between these are not simple or easy to implement or even use in many cases. It gets even worse when you need to implement a “reference semantics” type like this that is inappropriate to be a templated type, or contains a variety of internal references unrelated to the template arguments.

2. Now suppose I solve #1 and pass this into some class method, which then stores a copy of the const reference for later. But I as the caller have no way of knowing this. So after that method returns, I later proceed to modify the data via my non-const reference (which is a completely valid operation), but this violates the previous method’s assumption that the data was immutable (will never change). This creates an incredibly dangerous situation where later reads from that stored const reference to data (that was assumed to be immutable) is actually going to actually yield unpredictably changing results. Const is not immutable. Question: How do you make it so C++ guarantees that passed reference is truly immutable and will never be mutated so long as the reference exists, and enforce this guarantee at compile time? In Rust this is easy (in fact, it’s the default). In C++, it is impossible. The closest you can get in C++ are runtime checks, but that’s nowhere near as good as compile time checks.

Edit: Removed a bunch of perhaps unnecessary extra C++ trivia which I’ll save for later :)


This is actually funny because unlike C++, Rust doesn't have true immutable types. Instead Rust restricts mutability in two ways. One is immutable bindings, and the other is the shared references. Your `unique_ptr` is example is how it ought to work actually. That way you have the ability to parameterize by mutability which Rust cannot.

To be more clear, in Rust, the `mut` you see in bindings (like `let mut blah = ...`) is wildly different from the `mut` in `&mut T`. There was discussion on whether the latter should be renamed to `uniq` (because `mut` is misleading). In any event, Rust lacks `const` types. There is no `const T` like in C++. So something like this is impossible,

  std::vector<const int> vec;
You can't have just the elements of a collection to be immutable like the above example in Rust.

Secondly, a value of a type `&T` can be mutated internally (aka interior mutability). A function taking a `&T` type and mutating it behind the scenes is very whacky. AFAII, interior mutability exists only to trick the borrow checker using the `*Cell` types (which use unsafe internally BTW).


How would one make e.g. in Rust then ?

   #include <vector>
   int main()
   {
     std::vector<int> foo{1,2,3,4,5};
     foo.reserve(foo.size()* 2);
     for(auto it = foo.begin(), end = foo.end(); it < end; ++it)
       foo.push_back(*it);
   }


There are a couple of options:

- Repeating the vector using the built-in method `Vec::repeat`. This allocates a new vector, but `reserve` is likely to do the same, just implicitly.

https://play.rust-lang.org/?version=stable&mode=debug&editio...

- Using a traditional for loop. There are no lifetime issues, because a new reference is acquired on each iteration. The reserve is not required, but I included it to match your C++ version. https://play.rust-lang.org/?version=stable&mode=debug&editio...

- Creating an array of slices and then concatenating them into a new Vec using the concat method on slices: https://play.rust-lang.org/?version=stable&mode=debug&editio...


The most short and elegant example I could come up with that actually worked (since the example above really shouldn't work in Rust in the first place):

    fn main() {
        let mut v = vec![1, 2, 3, 4, 5];
        let new_size = v.len()*2;
        v = v.into_iter().cycle().take(new_size).collect();
    
        println!("{:?}", v);
    }
https://play.rust-lang.org/?version=stable&mode=debug&editio...


Other comments have answered this specific question, and I think it might be interesting to look at a similar-looking question that's actually more problematic for Rust. What I'll ask is, what's the Rust equivalent of this:

    #include <vector>

    void do_stuff(int &a, int &b) {
      // stuff
    }

    int main() {
      int my_array[2] = {42, 99};
      do_stuff(my_array[0], my_array[1]);
    }
That is, how do we take two non-aliasing mutable references into the same array/vector/view/span at the same time. (To be clear, none of the following applies to shared/const references. Those are allowed to alias, and this example will just work.) Notably, the direct translation doesn't compile:

    fn main() {
        let mut my_array = [42, 99];
        do_stuff(&mut my_array[0], &mut my_array[1]);
    }
Here's the error:

    error[E0499]: cannot borrow `my_array[_]` as mutable more than once at a time
     --> src/main.rs:7:32
      |                                                                 
    7 |     do_stuff(&mut my_array[0], &mut my_array[1]);
      |     -------- ----------------  ^^^^^^^^^^^^^^^^ second mutable borrow occurs here
      |     |        |
      |     |        first mutable borrow occurs here
      |     first borrow later used by call
      |
      = help: consider using `.split_at_mut(position)` or similar method to obtain two mutable non-overlapping sub-slices
The issue here is partly that the compiler doesn't understand how indexing works. If it understood that my_array[0] and my_array[1] were disjoint objects, it could maybe deduce that this code was legal. But then the same error would come up again if one of the indexes was non-const, so adding compiler smarts here wouldn't help the general case.

Getting multiple mutable references into a single container is tricky in Rust, because you (usually) have to statically guarantee that they don't alias, and how to do that depends on what container you're using. The suggestion in the error message is correct here, and `split_at_mut` is one of our options. Using it would look like this:

    fn main() {
        let mut my_array = [42, 99];
        let (first_slice, second_slice) = my_array.split_at_mut(1);
        do_stuff(&mut first_slice[0], &mut second_slice[0]);
    }
However, other containers like HashMap don't have `split_at_mut`, and taking two mutable references into e.g. a `HashMap<i32, i32>` would require a different approach. Refactoring our code to hold i32 keys instead of references would be the best option in that case, though it might mean paying for extra lookups. If we couldn't do that, we might have to resort to `Rc<RefCell<i32>>` or trickery with iterators. (This comment is too long already, and I'll spare the gory details.)

At a high level, Rust's attitude towards multiple-mutable-references situations is leaning in the direction of "don't do that". There are definitely ways to do it (assuming what you're doing is in fact sound, and you're not actually trying to break the aliasing rule), but many of those ways are advanced and/or not-zero-cost, and in extremis it can require unsafe code. Life in Rust is a lot easier when you refactor things to avoid needing to do this, for example with an entity-component-system sort of architecture.


Use a crate that provides safe functions implemented with unsafe code to do that, like https://docs.rs/splitmut/0.2.1/splitmut/


Neat! I bet we could add a macro to that crate to make it work with any (static) number of references. A variant of this using a HashSet to check arbitrary collections of keys might be cool too.


let mut v = vec![1,2,3,4]; for i in v.iter() { v.push(i);};

And that doesn't work, because the v.iter() is sugar for Vec::iter(&v), while the v.push(i) is sugar for Vec::push(&mut v, i) . I think it'd use deref coercion on the i, as Vec::iter(&v) gives you `i: &isize`. If this wasn't ints (or other `Copy` types, for that matter), you'd need to use .into_iter() to consume the Vec and get ownership of the entries while iterating, or use `.push(i.clone())` because `Vec<T>::push(&mut self, T)` is the signature, and you can only go automatically from `&T` to `T` for `Copy` types. Actually, it _may_ even need `v.push(*i)`, thinking about it. Try on https://play.rust-lang.org


So I don't have the same intuition for desugaring `vec!` that I do for desugaring `for (auto&& blah : blarg)`, but in either case if you desugar it the problem becomes a lot more clear. The Rust borrow checker errors I'm sure become second nature just like the C++ template instantiation ones do, but that is faint praise. To get some inscrutable C++ template instantiation error you have to mess with templates, and that's for people who know what they're doing. In Rust it seems like the borrow checker isn't for pros, it's C++-template complexity that you need to get basic shit done.

C++ is actually a pretty gradually-typed language, and I'm in general a fan of gradual typing. I don't mind that some people prefer BDSM-style typing, but IMHO that goes with GC e.g. Haskell a lot better than it does with trying to print something out or copy it or whatever.


It's not the same as C++ template errors. This is something that will directly cause a segfault in your code, that the Rust compiler is able to catch; AFAIK no C++ compiler would be able to catch that.


The problem here is that you’re trying to iterate over `v` and modify it at the same time. The usual fix is to first decide the changes that should be made, and then apply them after the loop:

https://play.rust-lang.org/?version=stable&mode=debug&editio...

Alternatively, you can loop over indices instead of values, which doesn’t require the loop to maintain a reference to `v` across iterations:

https://play.rust-lang.org/?version=stable&mode=debug&editio...


Thats the 3rd RFC in the trilogy. What has been shipped is a subset of the first RFC.


I mean, the author could have removed most of the complexity by using Rust HALs.

They just decided to reimplement from scratch their own different solution. That’s completely fair, but doesn’t allow you to make the complexity argument.

It would be like deciding to start a C++ project without using the C++ std library and arguing that C++ is hard because you had to reimplement std::Tuple...


> I mean, the author could have removed most of the complexity by using Rust HALs.

What are the "nrfXXXXX-hal" crates they use that provide the Port/Pin types then? What do the HALs provide in addition that would have helped?


The author cares. They got real cash for signing a meaningless token.


So why not ask for donations or sell prints?


Lifetime analysis doesn’t even show up in Rust compiler profiles. It takes 0 seconds.

Rust lifetime analysis is a function-local pass. It never needs to access any state outside the function. It can be done on all functions in parallel, etc.

You could do Rust alias analysis with 33 MHZ.


When somebody pays somebody to write those frameworks.

When nvidia sees a need, they can change CUDA over night to address it, and they pay people to do that.

When you need to do the same in Vulkan, that’s a multi year process till your extension is “open”. A teaser here whose job is to get something done with ML has better things to do than going through that.


Can you do screen sharing ?


I don't know if Teamviewer or Anydesk support *BSD, but you can always use xpra - though they say "All BSD variants should work, but are not regularly tested." so you might find a couple hiccups maybe.

I've found xpra --shadow to be almost on par with Anydesk on Linux - even passing through an ssh proxy. But obviously that might depend on GPU encoding which might not always be available on BSD.


I meant screen sharing on Teams, webex, zoom, etc


But distros do handle this for you. I installed Arch last year with sway and “it just works”.

Like the OP say, if you want HiDPI fractional scaling that’s a solved problem on Wayland. If you want to stick with X11, then that’s never going to work great


You keep repeating FUD.

Read the damn Editions RFC. The community agreed that no semantics or ABi breaking changes will land to Rust, EVER.

This is not a lesson from Py th on, but from C++, which introduces breaking changes every single release, which are much smaller than Python but still a pain in million LOc code bases.

If that ever happens, it was agreed that the result would be a different language, with a different name.

That is, editions don’t have this problem because the FUD that you are trying to spread every single time this issue comes up cannot happen, by design.

Your argument “Rust editions don’t solve this problem because they don’t handle semantic or ABI changes” is false, because in Rust there CANNOT be any semantics or ABI changes, and editions handle this situation just fine.

In the context of Python 2 vs 3 this argument makes even less sense, because editions allows combining libraries from different editions ina forward and backward compatible way without issues. Source: work on multiple >500kLOC Rust code base and one >1 million LOC and they all use crates from all editions, and mix & match them, without any issues, doing LTO across them, using dynamic libraries, and all possible combinations of binaries across 4 major platforms.


The problem is there, the fact you choose to ignore expectations of C and C++ enterprise developers using binary libraries across language versions is another matter.

You call it FUD, I call it hand waving.

I want Rust to succeed and one day be available as official Visual Studio installer language, but apparently unwelcome points of view are always pointed out as FUD and attacks.

When cargo does finally support binary crates, I will be glad to be proven wrong when linking 4 editions together into the same binary, just like I do with DLLs and COM today.


I think you misunderstood. C++ doesn't even have an official ABI, nevermind having a stable one. ABI changes can and do happen in many C++ implementations (and there is no compatibility across implementations - you can't link a library compiled with clang to one compiled with MSVC). You can't generally expect to link together libraries compiled with different major versions of the same toolchain, though this may be supported by some toolchains.

Instead, Rust has defined an ABI and has committed to never breaking that ABI. Editions support API-level changes, but the ABI won't change.


Rust has not defined an ABI. You're misunderstanding how the edition mechanism works. Each compiler knows how to turn source code of any given edition into the same internal IR, but that's purely internal. You still cannot compile an rlib with Rust compiler version 1.X and use it in a program compiled with Rust compiler version 1.Y. You can compile an rlib with Rust compiler version 1.Z that uses edition 2015 and use it in a program compiled with Rust compiler version 1.Z that uses edition 2018.


That is news to me, where is the ABI specified, given that cargo is yet to support binary crates?

> you can't link a library compiled with clang to one compiled with MSVC

You surely can, provided it is made available as DLL or COM.


Rust actually supports multiple ABIs and you can pick which one to use.

The one I use for maximum portability is the C ABI defined in the ISO C standard and on the platform docs (eg the Itanium ABI specified eg in the x86psABI document on Linux).


I didn’t chose to ignore that. I compiled a Rust binary library 4 years ago, it still works without recompiling today on a dozen of operating systems and toolchains that did not exist back then.

Try doing the same with C++.

I really believe you when you say that you are clueless about how to do this, since you don’t seem to have any idea about what you are talking about, and all your posts start with a disclaimer about that.

But at this point the only thing I have to tell you is RTFM. Doing this is easy. HackerNews isnt a “Rust for illiterates” support group. Go and read the book.


I wonder if PBRT will ever deliver GPU support.


The latest version v4 [0] has GPU support, some of my colleagues are using it already. Looks like the Rust version linked is based on v3 though.

"Support for rendering on GPUs is available on systems that have CUDA and OptiX."

[0]: https://github.com/mmp/pbrt-v4


>> The latest version v4 [0] has GPU support, some of my colleagues are using it already.

That's good news. I was trying to write a shader recently (not an OpenGL guy) to add the Fresnel term to an otherwise snells-law shader. It's easy to find the math, but hard to find a simple implemnentation. I'd like the full basic model with that and a roughness term with a proper reflectance function (Schlick should be fine) but again, it's hard to find code for what should be a common 50 line shader.


Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: