C vs C++, from the author of ZeroMQ



  • Yup.

    Of course you can tell syslogd not to bother logging any messages that look like the ones from the Ethernet driver, which I did as soon as I found out what was going on, but there's so much overhead involved in doing that as to make it pointless running the wire at gigabit speeds in the first place.

    The Beaglebone Black has only a 100Mb/s Ethernet port, not a gigabit one, but that's really all I need for an Internet router anyway; the cubietruck is back to a no-VLANs Ethernet configuration and happily serving files to the LAN at relatively decent speeds.


  • Banned

    @swayde said:

    This was neither. This was me fucking up, and getting shitty feedback from java.

    JVM counts as compiler to me.



  • @Gaska said:

    There are some things nothing can protect us from. When I say "always", I mean "except things I can't do anything about". Compiler bugs, executable code corruption and cosmic rays randomly shifting bits in RAM just happen.

    But you can do things about it. None of my examples are "bad code", corruption or the CPU misbehaving because of cosmic rays. I simply gave examples of someone using legal constructs of the language to break your expectations of their behavior. Things that are sometimes necessary because the real world is messy and languages that try to be perfect don't cut it in the real world.

    C++ is not widespread despite it's warts. It's widespread because of them. Even Rust, that tries to clean those warts, had to compromise with the unsafe keyword. You can't ignore that just because it doesn't suit you. It's part of the language, and it will be used.

    Your stance seems to be that unless you know everything, there is chaos and attempts to handle that are futile. My stance is that chaos is a given, and that you code to account for it.


  • Banned

    @Kian said:

    But you can do things about it.

    Only by being unreasonably paranoid.

    @Kian said:

    None of my examples are "bad code", corruption or the CPU misbehaving because of cosmic rays.

    I consider stupidity the same kind of inexorable disaster.

    @Kian said:

    C++ is not widespread despite it's warts. It's widespread because of them.

    Um, no, not at all. It's widespread because of source code compatibility with C. Just like PHP is widespread for no technical reason, but for availability of cheap web hostings in 2000s.

    @Kian said:

    Even Rust, that tries to clean those warts, had to compromise with the unsafe keyword. You can't ignore that just because it doesn't suit you. It's part of the language, and it will be used.

    In a well-formed Rust program, all unsafe blocks, while they can violate some guarantees, they should restore all those guarantees at the end of block. Doing anything else makes the program ill-formed - so doing this is sheer stupidity, which I consider an inexorable disaster you can't do anything about without invoking paranoia.

    @Kian said:

    Your stance seems to be that unless you know everything, there is chaos and attempts to handle that are futile.

    Not at all. In a typical C++ code, you can expect some things to work some certain way. You can, for example, expect operator+ to not mutate the operands. Everyone is subconsciously heavily depending on hundreds of these little assumptions, and everything usually goes well, because most code is mostly sane. But it's all because we believe in it. We believe GCC generates correct code. We believe boost::filesystem sees all files. We believe assignment operator makes a logical duplicate of rhs. We have no guarantee of any of it. We especially don't have a guarantee that exceptions won't be thrown by a particular piece of code, for example a constructor (it's changed with C++11 somewhat). I just took the fact that those beliefs are sometimes wrong, and taken to logical extreme (logical, as in such level of shattering dreams that still allows things to work).

    Rust explicitly gives some of these guarantees, so we can not only believe in them, but actually have a reason to believe. Rust raises our level of comfort from "I think this should work" to "this will work". It's a giant relief for all developers - both in writing new code (constant sanity checks made by compiler) and in using libraries (because the devs of this library went through the same process). Of course, it's all made possible because we believe people don't abuse unsafe. Which is as reasonable as expecting C++ programmers to enable warnings.



  • @Gaska said:

    Only by being unreasonably paranoid.

    If paranoia is the only way to bridge the gap between "possible" and "impossible", I wouldn't call it unreasonable.

    @Gaska said:

    Um, no, not at all. It's widespread because of source code compatibility with C.
    You don't think compatibility with C is a wart? You think Stroustrup wanted to design a language that opposes most of the conventions of C, but is required to compile C code with minimal modifications? C compatibility is a compromise in the language design. A wart. Given C++'s longevity, it was probably the right compromise at the time.

    C++ is not a better language for being compatible with C, despite all the effort that implied, but it is a more widespread one.

    @Gaska said:

    We especially don't have a guarantee that exceptions won't be thrown by a particular piece of code, for example a constructor (it's changed with C++11 somewhat).
    Um, what? Throwing an exception is the way constructors are meant to signal errors. It's why when you don't use exceptions, you have to create the object and then initialize it.

    Destructors shouldn't let exceptions escape them (it's fine if the destructor calls a function that throws, and catches that exception), so if you meant that and typo'd it, ignore the previous paragraph.

    Still, while being central to the design of C++, it is true that the proper way of using them isn't fixed. Exception specifications are one of the things that have changed the most, and are still somewhat in flux, though it seems they're finally finding a reasonable way of specifying them. Which mostly amounts to making a promise that a piece of code won't let an exception escape it, and calling terminate if it violates that promise.

    But ok, you're saying that misusing "unsafe" amounts to undefined behavior. I agree that trying to fight undefined behavior is pointless. That still leaves the problem of having the entire behavior documented. It might be possible in Rust, but you are eventually going to call something that is not Rust, by linking to a dll for example, and other languages don't offer the same guarantee of safety that Rust does. A C function could return anything it wants as an error, and that would be well specified behavior for that language.

    And your Rust program is sitting on top of an operating system written in C or C++, most likely. So until you design a Rust operating system, you can't really say that you know what every error returned from every call you make could be.


  • Banned

    @Kian said:

    You think Stroustrup wanted to design a language that opposes most of the conventions of C, but is required to compile C code with minimal modifications?

    That's EXACTLY how it was. Why do you think the Most Vexing Parse is a thing?

    @Kian said:

    C++ is not a better language for being compatible with C, despite all the effort that implied, but it is a more widespread one.

    Agreed. It's not better, it's worse. But you seem to underestimate how big role C compatibility played in popularizing C++.

    @Kian said:

    Um, what? Throwing an exception is the way constructors are meant to signal errors. It's why when you don't use exceptions, you have to create the object and then initialize it.

    What are the possible errors class Vec2f { float x, y; } can encounter in constructor? If you're a reasonable person expecting reasonable behavior, you'd say none. But in C++98/03, anything can throw anything, so you must prepare for this constructor to throw as well.

    Exceptions in constructors are especially nasty because they leave behind a half-constructed object in indeterminate state.

    @Kian said:

    It might be possible in Rust, but you are eventually going to call something that is not Rust, by linking to a dll for example, and other languages don't offer the same guarantee of safety that Rust does.

    FFI in Rust requires unsafe, for exactly this reason ;)

    @Kian said:

    So until you design a Rust operating system

    It's on the way already 😛



  • @Gaska said:

    That's EXACTLY how it was.

    @Gaska said:

    But you seem to underestimate how big role C compatibility played in popularizing C++.

    Umm, I don't think you are understanding me. Stroustrup wanted a language that gave the user the ability to create their own abstractions, types that behaved essentially the way built in types could, but that also provided zero overhead for those abstractions, and access to the hardware when necessary.

    Aside from what he expected of the language itself, he wanted his language to be used by people. He didn't choose to bolt things on to C because that was the easiest way to achieve his goals on the language front. He did it because it was the best way to achieve widespread use in the demographic he was targeting the language at.

    So I'm not underestimating how big a role C compatibility had. I'm doing the opposite, I'm saying C compatibility is only there so that C++ would be popular. The only benefit C compatibility offered was popularity, but it was a valuable enough benefit to sacrifice the syntax of the language to it.

    @Gaska said:

    Exceptions in constructors are especially nasty because they leave behind a half-constructed object in indeterminate state.
    Remember when I said I had to correct you about basic concepts, and how that kind of lowered the merit of your arguments? This is another BASIC concept regarding exceptions that you misunderstand in a critical manner.

    The result of throwing in a constructor is that the object doesn't exist. It is impossible to refer to an object that threw in the constructor. The syntax itself doesn't allow it. Simple example:

    class MyClass;
    
    void Function1()
    {
      MyClass anObject;
      DoSomething(anObject);
    }
    

    If the constructor of anObject throws, anObject is never constructed, and DoSomething is never called. Instead, the caller of Function1 simply sees the exception, and the exception will continue to propagate until a handler catches it. Another example:

    void Function2()
    {
      try {
        MyClass anObject;
      }
      catch(MyException& e) {}
      // DoSomething(anObject); // This is a syntax error. 
                                // anObject doesn't exist in this scope
    }
    

    Constructor exceptions are the way you avoid having zombie objects. With exceptions, you either have a valid object, or an exception. You can't have zombie objects. Trying to refer to an object that wasn't constructed is a syntax error.


  • Banned

    @Kian said:

    He didn't choose to bolt things on to C because that was the easiest way to achieve his goals on the language front. He did it because it was the best way to achieve widespread use in the demographic he was targeting the language at.

    I know it all. But it doesn't change the fact that he made a goal of being able to compile C as C++, he achieved it, and the language ended up much worse than it could because of it.

    @Kian said:

    The only benefit C compatibility offered was popularity, but it was a valuable enough benefit to sacrifice the syntax of the language to it.

    I think it wasn't worth it in the long run, after all.

    @Kian said:

    It is impossible to refer to an object that threw in the constructor.

    Whenever you have the throwing object in the same scope as the try block, you can. For example, if it's a class member, or if using placement new. However, when I was trying to reproduce it in Ideone, I kept getting SIGABRT whenever I managed to get half-initialized object. I blame using a hard-coded case and compiler optimizing it out. Too bored for more testing, though.



  • Can you show an example of what you mean? The syntax you used?



  • @Gaska said:

    I know it all. But it doesn't change the fact that he made a goal of being able to compile C as C++, he achieved it, and the language ended up much worse than it could because of it.

    Which is the point I made in the first place. C++ is popular not in spite of its warts, it's popular because of them. Glad that you now agree.


  • Banned

    @Kian said:

    Can you show an example of what you mean? The syntax you used?

    #include <iostream>
    using namespace std;
     
    class Motherfucker {
    	std::string s;
     
    	public:
    	Motherfucker() {
    		s = "Fuck the system";
    		throw "They try to catch me";
    	}
     
    	void iDoWhatIWant() {
    		cout << s << endl;
    	}
    };
     
    class Russkiy {
    	Motherfucker mofo;
     
    	public:
    	Russkiy() try: mofo() {} catch(...) {}
    	void shtoTyDielosh() {
    		mofo.iDoWhatIWant();
    	}
    };
     
    int main() {
    	Russkiy wania;
    	wania.shtoTyDielosh();
    }
    

    The placement new example would be rather straight forward.

    @Kian said:

    Which is the point I made in the first place. C++ is popular not in spite of its warts, it's popular because of them. Glad that you now agree.

    Well, at first, I thought you meant all the C++ warts, not just the C legacy-related ones. Because C++ has many warts unrelated to C - for example, stateful globals for printing to standard streams, overloading bitshifts to mean something entirely else, implicit constructors shitfest, const_cast, auto_ptr, generator-unfriendly range-for in C++11, etc. These certainly don't add to its popularity.



  • Your code crashes because you're not catching the exception you're throwing.
    @C++ Standard §15.3p15 said:

    The currently handled exception is rethrown if control reaches the end of a handler of the function-try-block of a constructor or destructor.


  • Banned

    Oh, I see. So the only way to get half-initialized object is either placement new, or a buggy custom allocator.

    Score for exceptions.

    But on further thought, there's still the problem of destructor handling constructor's exception.



  • I didn't think anyone else was still reading this thread. And yet, I got :hanzo:'d.

    Yeah, you can't catch exceptions in a constructor. Or rather, you can catch them, but the standard mandates that the catch block in the constructor rethrow the exception. A more detailed explanation: http://www.gotw.ca/gotw/066.htm

    @Gaska said:

    But on further thought, there's still the problem of destructor handling constructor's exception.

    Not really. The destructor is only called for objects whose lifetime is at an end. An object's lifetime starts after the constructor ends successfully. If the constructor throws, the lifetime of an object never started, and thus it can't end, so the destructor is not called.

    If an object holds objects of its own, and one throws halfway during construction, only the destructors for the objects that managed to be constructed will be called, in reverse order of construction.



  • @Gaska said:

    But on further thought, there's still the problem of destructor handling constructor's exception.

    The destructor is not called if the constructor throws. Destructors are only called on constructed objects and if the constructor throws the object isn't constructed.


  • Banned

    TIL.

    Also fuck you Discourse, I'm descriptive enough!



  • CAPITAL LETTERS ARE NOT DESCRIPTIVE!
    <lalala>


  • Banned

    Til.



  • YOU STILL LEARNED IT WRONG
    <Seriously...>



  • @Gaska said:

    So the only way to get half-initialized object is either placement new,

    I don't think this is a fair assessment. Essentially, placement new lets you try to construct an object in an arbitrary position in memory. Meaning you need to create a buffer, and then call placement new on that buffer. This will try to create an object at that position.

    If the constructor throws, then you don't have a "half-constructed object". You have a block of memory that didn't hold an object before, and still doesn't hold an object now.

    On the plus side, you don't need to call the destructor on that block of memory either (if you create an object with placement new, you are responsible for calling the destructor too).


  • Banned


  • Discourse touched me in a no-no place

    @Kian said:

    Yeah, you can't catch exceptions in a constructor.

    Of course you can, but in doing so you mustn't leave the object in a partially constructed state. This isn't an issue with simple object construction scenarios, but with more complex situations it is clearly something that matters.

    For example, consider an object that represents a TCP connection to a remote host where that host is specified by its DNS name, not an unreasonable thing. Failures may happen during the lookup of the name, or during the establishment of the connection (there's a lot of failure modes, especially when you consider that you might need to try connecting with both IPv6 and IPv4). But from the outside, all that you see is you either create an object that is correctly connected, or you get an exception telling you what caused a failure that the code itself couldn't recover from: the caller can avoid having to know much about just how nasty networking really is. And that's the whole damn point.



  • I approve.



  • @dkf said:

    Of course you can,

    How did you manage to focus on that sentence, while missing the sentence above (where I mention I was ninja'd by @nodrefrofr, who posted an excerpt from the C++ standard) and the following sentence that clarified
    @Kian said:

    Or rather, you can catch them, but the standard mandates that the catch block in the constructor rethrow the exception.

    I'd consider it pendantry if you had bothered to clarify the situation where what you say makes sense, but you didn't so I'm assuming you ignored the context of the conversation up to this point, which goes over your objection already. So I'll make it more clear.

    Yes, the constructor body can throw, and you can catch the exception within the constructor body. This is not interesting, and to an external observer it's the same as if the constructor had not thrown. We were talking about a situation where an exception might leave a half constructed object.

    Gaska brought up the rather obscure syntax for catching an error in an initializer list (point to him, I had to look it up), the function try block. What nodrefrofr and I pointed out was that the function try block can catch exceptions, but in the context of constructors and destructors it has to rethrow them. If you omit the throw clause, the compiler will just do it for you. Which is why his attempt to build a zombie object failed at runtime.


  • Discourse touched me in a no-no place

    @Kian said:

    How did you manage to focus on that sentence, while missing the sentence above

    I've had that sort of day. 😫


  • :belt_onion:

    @Gaska said:

    ```
    FILE* f = fopen("dupa");

    
    
    
    
    
    
    I see what you did there.

  • Java Dev

    @obeselymorbid said:

    @Gaska said:
    ```
    FILE* f = fopen("dupa");

    
    I see what you did there.</blockquote>
    
    Wrote code that doesn't compile?

  • Banned

    Give me a break. I haven't used fopen() in years.


  • Banned

    On further thought, how do you know which fopen() I was using? Maybe my fopen() accepts just one argument? 🕶


  • Java Dev

    Because any alternative would be TR :WTF:?



  • I was curious about how Rust handled object construction, since as you say they use errors. My understanding was that Rust had destructors, but having destructors without exceptions seems to create problems.

    So, a quick search says that there's no "constructors" per se, but instead there's a convention to declare static methods that return objects of the correct type. Which raises the question of just how the type is created inside the constructor. I imagine the constructor returns an error or the object, so if you fail you have an error.

    Would it be accurate to say that types are simply structures, and you can create one without calling a constructor? Is there a way to say "only create an object of this type through the constructor function"?

    I imagine if you create it through the constructor, you have to check that the constructor succeeded (which as you say, raises a compiler warning if you don't). But is there anything that stops you from treating it as if it had succeeded afterwards? That is to say, in pseudo code:

    let x = TypeA::constructor();
    if (x == error) { doSomething(); }
    // Can I refer to x here as if constructor had succeeded?

  • Banned

    @Kian said:

    My understanding was that Rust had destructors, but having destructors without exceptions seems to create problems.

    Yeah, Rust can't signal any errors in destructors. It gets worse when you realize that destructors aren't guaranteed to run. They used to be, but they found a terrible memory safety bug related to it. Mind you, destructors not running is a very rare case - but you can't really depend on it. However, most destructors are there to free memory after object is destroyed, and it isn't that bad to leak some memory if the goal is to never segfault. There is an ongoing work in how to make it possible to explicitly guarantee that on some objects - it turns out to be quite non-trivial. Remember that those guys are a bit crazy about good design a safety - the current hottest topic in the community is that ref-counting pointer is unsafe to use if the ref counter overflows.

    @Kian said:

    Would it be accurate to say that types are simply structures, and you can create one without calling a constructor?

    Well, yes. Structures in any language are just pieces of memory that you arbitrarily give some specific semantics.

    The trick is, you need to have all the fields of a struct accessible to create it, so unless they're all declared public, only the module the struct is declared in can directly construct it. If there's at least one private function, the type cannot be constructed otherwise than via a public function that comes from this module and returns an object.

    @Kian said:

    let x = TypeA::constructor();
    if (x == error) { doSomething(); }
    // Can I refer to x here as if constructor had succeeded?

    No, because x holds the error in this case, not the object.

    BTW, idiomatic name for constructor function is new.


  • I survived the hour long Uno hand

    @Gaska said:

    it isn't that bad to leak some memory if the goal is to never segfault

    I wouldn't want the language designer making that decision for my application though.



  • @Gaska said:

    the current hottest topic in the community is that ref-counting pointer is unsafe to use if the ref counter overflows.

    Don't they have any bikesheds to paint, instead? A ref-counted pointer should have a counter that is the same size as the pointer itself. If that overflows, it means you have more references than actual memory locations. How is the pointer theoretically supposed to overflow?

    @Gaska said:

    No, because x holds the error in this case, not the object.

    I understand x holds the error. That's my concern. What x holds is defined at runtime. So can I only access x from within some structure (I mean like an if block here) that checks the type? Is that checked by the compiler?


  • ♿ (Parody)

    @Gaska said:

    ref-counting pointer is unsafe to use if the ref counter overflows.

    How big is the ref-counter?



  • @Gaska said:

    Structures in any language are just pieces of memory that you arbitrarily give some specific semantics.

    Yeah, but the semantics in C++ are that you can't create an object except through its constructor. That's pretty different from "only if you mark something as private," as in Rust. It's an odd choice.

    Doesn't that mean you can't create something "in place"? It always has to be obtained as the return of a function? What about objects contained inside an object you have to? You have to manually call each of their constructors as well?


  • Banned

    @Yamikuronue said:

    I wouldn't want the language designer making that decision for my application though.

    That's what they're trying to do - give a programmer a way to say "hey, make sure those destructors do run, okay?". But apparently, it's so hard it didn't make into 1.0.

    @Kian said:

    Don't they have any bikesheds to paint, instead? A ref-counted pointer should have a counter that is the same size as the pointer itself. If that overflows, it means you have more references than actual memory locations. How is the pointer theoretically supposed to overflow?

    Here's the discussion. They use forget() function, the purpose of which is to throw a value out of scope without running a destructor - so at the time you have only a single Rc in memory, but "logically" you have many of them because they're not destroyed. Probably the most corner-case-y corner case imaginable, but still causes segfaults in safe code, which is unforgivable for some reason. They're little weird, those Rust devs.

    @Kian said:

    I understand x holds the error. That's my concern. What x holds is defined at runtime. So can I only access x from within some structure (I mean like an if block here) that checks the type? Is that checked by the compiler?

    Actually, x is a Result<TypeA, ErrorA>. This Result is an enum (a tagged union actually) with two variants - It's either Ok(o), in which case you can access o, which is of type TypeA, or Err(e) with e being ErrorA. In the latter case, you can't acces o, because it simply doesn't exist.

    @boomzilla said:

    How big is the ref-counter?

    Processor word size, which is 32-bit on x86. Apparently, they're okay with this bug on 64-bit architectures, because creating 264+1 Rcs takes unrealistically long time to happen, but with 32-bit counter it's under a minute.

    @Kian said:

    Yeah, but the semantics in C++ are that you can't create an object except through its constructor. That's pretty different from "only if you mark something as private," as in Rust. It's an odd choice.

    It's not odd at all. The rule doesn't say "you can't instantiate something if it has private parts and you're not in the same module". The rules say "you must assign a value to all fields on instantiation" and "you can't access private parts from outside the module" - both of them are blatantly obvious and make perfect sense in separation, and when combined, the Rust way to enforce constructors is blatantly obvious and makes perfect sense as well. That's what I like about Rust - they make few language rules, but with very deep impact. Function arguments, local variable declarations, match statement and if lets all use the same syntactic elements and follow the same semantical rules.

    @Kian said:

    Doesn't that mean you can't create something "in place"?

    You can, but only according to the two rules I mentioned above. But then, it is really that different from mandatory constructors in C++?

    @Kian said:

    What about objects contained inside an object you have to? You have to manually call each of their constructors as well?

    Why, yes. You must do the same in C++. How else do you imagine doing things?



  • @Gaska said:

    They use forget() function, the purpose of which is to throw a value out of scope without running a destructor - so at the time you have only a single Rc in memory, but "logically" you have many of them because they're not destroyed.

    So if I get this right, this is a consequence of not being able to guarantee destructors get called?

    @Gaska said:

    Actually, x is a Result<TypeA, ErrorA>. This Result is an enum (a tagged union actually) with two variants - It's either Ok(o), in which case you can access o, which is of type TypeA, or Err(e) with e being ErrorA. In the latter case, you can't acces o, because it simply doesn't exist.
    I can follow all that until the last sentence. I understand that o doesn't exist at runtime, because the constructor failed.

    My question is, what prevents me from assuming the constructor succeeded. That is, in this code:

    let x = TypeA::new();
    // What does the code here look like that I can't refer to the TypeA object in x
    

    Short of the compiler running a bit of static analysis on every such call, which I imagine would add to the compilation time, it shouldn't be detectable.

    Sorry to be asking you. I looked here: http://rustbyexample.com/custom_types/enum.html but none of the code there, that I could see, dealt with errors at all.



  • @Gaska said:

    Why, yes. You must do the same in C++. How else do you imagine doing things?
    Well, you can obviate calls to default member constructors that take no arguments in your constructor. This way feels like it requires a bit more boilerplate in some situations.


  • Banned

    @Kian said:

    So if I get this right, this is a consequence of not being able to guarantee destructors get called?

    Yes and no. Yes because if destruction was mandatory, it wouldn't ever overflow without running out of memory. No because forget() is included in standard library because it serves a purpose - there are some legitimate cases where you really don't want to run destructor, so it should be possible to opt-in for it - and then you get this here problem.

    The discussion about guaranteed destruction is far more interesting than about this bug - partially because it's harder problem, but mostly because it actually matters and the problem is very real. CBA to find it.

    @Kian said:

    My question is, what prevents me from assuming the constructor succeeded.

    The value of x is Err and not Ok. Simple as that.

    @Kian said:

    Well, you can obviate calls to default member constructors that take no arguments in your constructor. This way feels like it requires a bit more boilerplate in some situations.

    That's because in C++ you can have uninitialized variables (they default to default constructor) and in Rust you can't.


  • Java Dev

    @Kian said:

    My question is, what prevents me from assuming the constructor succeeded.

    You can't get at either value directly. You can only get at o or e in an appropriate match statement, and only the applicable branch (Ok(o) or Error(e), depending on whether the call succeeded) will run.



  • @Gaska said:

    The value of x is Err and not Ok. Simple as that.

    This doesn't answer my question.
    @PleegWat said:
    You can only get at o or e in an appropriate match statement, and only the applicable branch (Ok(o) or Error(e), depending on whether the call succeeded) will run.

    This does.

    However, if this is so, why can't you guarantee the destructor will always run? Can you point to discussion that explains why you sometimes don't want to run the destructor? You like the elegance of simple rules, and the rule "if the constructor succeeded, the destructor runs" looks pretty simple and elegant. Adding a "sometimes" there is a pretty big cost, and as the ref counted pointer shows, it breaks the RAII idiom.


  • Banned

    It's a kinda hacky, but very impactful corner case. TBH, it's beyond my understanding why you would want either to happen - but trust me, there are uses.


  • Banned

    @Kian said:

    This doesn't answer my question.

    @Kian said:

    This does.

    And how was it different from what I said before?



  • @Gaska said:

    And how was it different from what I said before?

    You said:

    This Result is an enum (a tagged union actually) with two variants - It's either Ok(o), in which case you can access o, which is of type TypeA, or Err(e) with e being ErrorA. In the latter case, you can't acces o, because it simply doesn't exist.
    What was missing was

    @PleegWat said:

    You can only get at o or e in an appropriate match statement,
    Which you didn't explain clearly enough for me to understand (perhaps you mentioned it in passing earlier, and took it for granted that I remembered and understood it now). I was asking how the syntax and semantics of the language specifically kept me from the wrong value, and you just told me it did.

    The experience I have with unions is that you SHOULD only read what you put in, but the syntax lets you access any of the members. So when you say "an enum is a tagged union", that doesn't tell me you can only access the members through a specific construct that checks the tags. It only tells me that there's a tag you can check.


  • Banned

    @Kian said:

    The experience I have with unions is that you SHOULD only read what you put in, but the syntax lets you access any of the members.

    If your experience is limited to C, then this is understandable - C unions are totally fucked up. But just like with errors, you shouldn't assume that if some feature is totally fucked up in C and doesn't exist in Java, then it must be totally fucked up in every other language implementing it as well. Tagged unions exist in many languages, and almost all of them handle them sanely.

    I didn't think that you wanted a code sample - I assumed that you either know what matching syntax is in Rust (it doesn't take long to google and understand it), or you don't want to learn Rust and just want to know it on the concept level. When I said "doesn't exist", I meant there's no syntax available to access it, because, well, it doesn't exist. If you never declared foo, you can't use foo either.


  • Discourse touched me in a no-no place

    @Gaska said:

    It's a kinda hacky, but very impactful corner case.

    I think he'd be satisfied with a link to the discussion. Sometimes that's just the easiest way.


  • Banned

    Yeah, if I could find it. I'm not really a bookmarking person. I'm sure he can find it himself if he's determined enough.


  • Discourse touched me in a no-no place

    @Gaska said:

    I'm not really a bookmarking person.

    Nor am I; the Googlezzzzz are effective, but you know better what you're talking about than he does.


  • ♿ (Parody)

    @Gaska said:

    >How big is the ref-counter?

    Processor word size, which is 32-bit on x86. Apparently, they're okay with this bug on 64-bit architectures, because creating 264+1 Rcs takes unrealistically long time to happen, but with 32-bit counter it's under a minute.

    Hmmm....wow...how do they create that many without running OOM?


Log in to reply