Who needs trigger warnings anyway? (Trigger Warning: trigger warnings)


  • Banned

    @Kian said:

    It doesn't even have anything to do with immutability. You could have COW for mutable objects.

    If you design your data structures specifically for COW, you can do better than by just tacking on COW after the fact.

    But being able to do the latter is nice nonetheless.

    @Kian said:

    It's just lazy copying.

    Well, duh. In other news, class inheritance is just function pointers.

    @Kian said:

    You are still not allowed to mutate an object that is const, or through a const reference or pointer to const.

    Actually, the standard specifically guarantees that it is perfectly safe to const_cast a const pointer/reference into non-const and then do whatever changes you want to the underlying object, as long as the object was originally created as non-const <abbr title=Actually, I'm not sure whether const-casting references to rvalues is safe or not. For the sake of my own sanity, I assume it's not.">lvalue.

    @Kian said:

    The rules can get complex, but that's intended to make it straight-forward to use.

    Like operator overloading.

    @Kian said:

    Well, this is an issue of language I suppose.

    Yes. A spoken language, not programming language. Also, an issue of being stuck in the von Neumann mental model of programming.

    @Kian said:

    In C++, an object is defined by the space it occupies in memory.

    If you aren't a compiler programmer and aren't on the verge of triggering UB, you do not need to know this.

    @Kian said:

    An object's lifetime starts when memory is allocated for it and any non-trivial constructor finishes

    More general, and just as correct, is a sentence formed by taking this quote and throwing away everything between "memory" and "trivial", inclusively.

    @Kian said:

    During it's lifetime, the object is the memory it occupies.

    During its lifetime, the object is the object. Going lower carries a very high risk of leaking abstractions.

    @Kian said:

    actually have two objects

    @Kian said:

    logically they are one single abstraction

    Either you don't understand what abstraction is, or don't realize what benefits it gives you.

    @Kian said:

    Tell your library vendors to fix their shit

    Do you really think it's that easy?

    @Kian said:

    That's literally how const works in C++.

    No it's not. const creates a distinct type. You can check by trying to specialize a template function for both int and const int - it will compile.

    @Kian said:

    How is having a const overload for certain access methods more annoying than having an iter and iter_mut method?

    You have to do it much less often, and unlike C++, in Rust it's obvious why a particular method was repeated for both constnesses. Also, there aren't templates in Rust.



  • @LB_ said:

    having two overloads with the same code and one is const while the other isn't is a pretty lame aspect of the way const correctness is implemented in C+

    I've been dreaming of a simple language feature where I can write

    some_type const? & get_thingie() const?;
    

    which simply creates two member functions, one with all the "const?"s replaced by "const" and one with all the "const?"s removed...



  • @Gaska said:

    But being able to do the latter is nice nonetheless.

    The point being, it has nothing to do with immutability.

    @Gaska said:

    Actually, the standard specifically guarantees that it is perfectly safe to const_cast a const pointer/reference into non-const and then do whatever changes you want to the underlying object, as long as the object was originally created as non-const lvalue.
    Ugh. I looked it up, I wasn't familiar with that technicality. What the type system gives you, the casts take away.

    Of course, there's the big caveat that you have to know the reference is not bound to a const value, and that is either impossible or trivial. If you use the cast when it's impossible to know, such as for function parameters, you are trusting the correctness of you program to chance. If it's trivial (you can see the declaration of the variable), you have easier ways of getting to the value and changing it.

    @Gaska said:

    More general, and just as correct, is a sentence formed by taking this quote and throwing away everything between "memory" and "trivial", inclusively.
    Less general, you mean. After all, when you declare some kinds of values without initializing them, no constructor is run but lifetime for the object starts. Also, if you call a constructor on memory you haven't reserved for an object (through placement new, for example), that's UB.

    Something that covers everything -> more general. Something that leaves stuff out -> less general.

    Also, I wouldn't say this is relevant only to compiler writers. It's important for correcting people on the internet.

    @Gaska said:

    During its lifetime, the object is the object.
    The first rule of tautology club is the first rule of tautology club.

    @Gaska said:

    Either you don't understand what abstraction is, or don't realize what benefits it gives you.
    If I'm using the abstraction, I don't care about the implementation. If I'm writing the abstraction, I have to care about the implementation since that's my job. Sometimes I use abstractions others write, sometimes I provide abstractions for others to use.

    @Gaska said:

    No it's not. const creates a distinct type.
    What do you think a type is, if not something you can tell about a variable? In other words, how is it functionally different? How is constness in Rust not part of the variable's type?



  • @anonymous234 said:

    @LB_ said:
    In certain other languages, this just declares a outright. That's extremely dangerous: a simple typo is all it takes to make your code wrong and yet it compiles without errors, or in some cases it even compiles without warnings!

    Wanna hear something funny?

    In Python,

    def f():
        print(a)
    a = 3
    f()
    

    works as you might expect (prints 3), but this

    def f():
        print(a)
        a = 5
    a = 3
    f()
    

    just throws UnboundLocalError: local variable 'a' referenced before assignment.

    Because by assigning to a anywhere in the function, you are defining a local variable named "a". So now you can't access the global variable a in that function.

    As much as everyone here hates PHP, this is one of things that I think PHP got right. When you're inside a function (including class/instance methods), you have exactly one scope. If you want access to a variable in the global scope, you have to have an explicit global declaration or use the GLOBALS array (i.e. a strong indicator that you might be doing something wrong). There's never a need to try to figure out which scope your $a is coming from.



  • @boomzilla said:

    http://crystal-lang.org/

    Ruby-inspired syntax.

    :nope.mdb:

    If the syntax in Crystal is inspired by Ruby the same way that the syntax in Ruby is inspired by Perl, then I expect alphanumeric characters to be completely disallowed.

    :do_not_want:

    PS: How can there not be a :do_not_want:


  • Banned

    @Kian said:

    The point being, it has nothing to do with immutability.

    Immutable data structures by their very nature must be COW (or rather, behave like COW).

    @Kian said:

    Less general, you mean.

    No, I mean more general. As in, applies in more cases and covers more ground.

    @Kian said:

    After all, when you declare some kinds of values without initializing them, no constructor is run but lifetime for the object starts.

    OK, I used the wrong word. Instead of constructor, I should have said initialization. To all gramming nazis out there - of course I should have rewrite the sentence a little too!

    Initialization is usually done with a constructor. Usually it involves allocating memory. But in fact, initialization doesn't imply either - you have some abstract variable that might or might not be in memory, and you prepare it for use, which might or might not run some code.

    On the defence of my original statement, non-primitives always have constructor, trivial or otherwise, and it's impossible to obtain an object that didn't have any constructor run without ending up with ill-formed code. It is, however, construct an object without allocating any memory (for example - reusing memory previously occupied by something else), so technically speaking, my sentence was still correct.

    Remember that trivial constructor is still a constructor. Kind of an inlined empty function.

    @Kian said:

    Also, if you call a constructor on memory you haven't reserved for an object (through placement new, for example), that's UB.

    You can reserve memory by declaring an array of chars. Or syscall. Or using padding of some structure. There are many, many ways to acquire memory in legal C++ that one shouldn't ever do and that look like UB at first glance.

    @Kian said:

    The first rule of tautology club is the first rule of tautology club.

    Yes it is. An object is tautologically defined as an object. Anything more concrete is either implementation detail, language lawyering (which is by definition tied to a specific language), or von Neumann brain worm that prevents C++ coders from writing good code, just like OOP brain worm prevents writing good code by Java coders.

    @Kian said:

    If I'm using the abstraction, I don't care about the implementation. If I'm writing the abstraction, I have to care about the implementation since that's my job.

    Are you writing C++ compiler? Then why do you care that object occupies memory?

    @Kian said:

    What do you think a type is, if not something you can tell about a variable?

    Something you can tell about a value, perhaps?

    @Kian said:

    How is constness in Rust not part of the variable's type?

    Unless we're speaking about references (which are more like C++ pointers than references, in that they're very explicit and you can have reference to reference), then the constness of a variable is a feature completely separate from the type of a variable. A declaration of a variable goes like let mut a: B = c, where a is name, B is type, c is value (usually B can be inferred from c), and mut is or isn't there (determining whether a is mutable or not). Variable a has the exact same type as value c (and I mean it in the most literal way possible). Whether c is mutable or not is irrelevant because the mutability is defined by mut, not by type. Mutability never occurs inside type definition (unless it's a reference, but I said that we're not speaking of these). The type has nothing to do with determining whether a is mutable or not. It's orthogonal concept.


  • Java Dev

    @Dragnslcr said:

    As much as everyone here hates PHP, this is one of things that I think PHP got right. When you're inside a function (including class/instance methods), you have exactly one scope. If you want access to a variable in the global scope, you have to have an explicit global declaration or use the GLOBALS array (i.e. a strong indicator that you might be doing something wrong). There's never a need to try to figure out which scope your $a is coming from.

    Well, there is also the global keyword. Doesn't affect your case much.

    function f()
    {
        global $a;
        $a = 3;
    }
    


  • Right, that's what I meant by "explicit global declaration". And I consider it a Bad Thing (TM).



  • @Gaska said:

    Depends on what you mean by addressing.

    Uniquely identifying?

    @Gaska said:

    I can still get my work done just fine, which further reinforces their point.
    Yeah, people get work done in Python and PHP and JavaScript every day. That doesn't mean those languages are good at protecting you from accidentally violating constraints. Those languages have nightmare type systems.

    @Kian said:

    Same if you have a larger structure with pointers or references. The stuff those pointers or references point to are not part of the object that holds the references or pointers, from a language standpoint.
    Careful - the fact that references can occupy memory in some cases is an implementation detail.

    @Kian said:

    That's literally how const works in C++.
    No, it's actually subtly different. I haven't decided which I prefer, but I like both.

    @ixvedeusi said:

    IMHO there's one valid use for const_cast, too, and it is precisely to avoid repeating long member function bodies for const and non-const cases:
    Undefined behavior is not a valid use!

    @dkf said:

    Handling “all cases” is frequently harder than it appears to be at first glance.
    I guess what I want is an "all cases" std::string and then maybe a std::cow_string for when the programmer knows better. This is how much of the rest of C++ is designed - e.g. you choose std::vector first and then if you profile your code and find that (amazingly) std::list is faster in that particular case, you can switch.

    @Gaska said:

    If you aren't a compiler programmer and aren't on the verge of triggering UB, you do not need to know this.
    So I should never need to serialize possibly cyclic data structures? Sorry, I don't agree: there are some good use cases for using address as object identity.

    @Kian said:

    Less general, you mean.
    Actually I agree with @Gaska here - I only consider object lifetime as the time between the constructor ending to the destructor starting. Obviously objects are composed of objects and if you go deep enough you get to raw byte objects, and that's where you can consider their lifetime as being from alloc to free.



  • @Gaska said:

    Immutable data structures by their very nature must be COW (or rather, behave like COW).

    "Must" be COW? You can have immutable structures that just "copy on copy". Lazy copy is an optimization for a specific use case, not a requirement of immutability.

    @Gaska said:

    OK, I used the wrong word. Instead of constructor, I should have said initialization.
    You're still wrong. Look, I prefaced my description with the very clear qualification that I was talking about C++. If you want to talk about objects "in general", that's fine. But your generalizations don't apply to C++. C++ is more nuanced than what you are saying.

    @Gaska said:

    You can reserve memory by declaring an array of chars. Or syscall. Or using padding of some structure. There are many, many ways to acquire memory in legal C++ that one shouldn't ever do and that look like UB at first glance.
    I know. I was pointing out why there are two requirements and why both requirements must be met, by describing situations in which either one or the other isn't met. You can get memory without calling a constructor, and you can call a constructor without getting memory. To have an object, you have to do both in order.

    @Gaska said:

    Anything more concrete is either implementation detail, language lawyering (which is by definition tied to a specific language)
    I was describing what the memory model for C++ is. Of course it's applicable only to C++.

    @Gaska said:

    Are you writing C++ compiler? Then why do you care that object occupies memory?
    Because memory access is expensive. C++ makes a lot of sacrifices for performance. Ignoring the fundamental memory model of the language means all those sacrifices are wasted.

    Something as simple as the order in which the members of an object are declared in C++ can alter the size of the object, as the language mandates padding where the alignment and size of members creates gaps. If you can squeeze more objects per cache line by eliminating wasteful padding, you get better performance. So there's a material gain to understanding the memory model.

    Then you get into concurrent programming, and the correctness of your program relies on your understanding of the model. I'll point out, people get races and similar issues even in safe languages. Understanding how your language of choice works isn't a pointless exercise.

    @Gaska said:

    The type has nothing to do with determining whether a is mutable or not. It's orthogonal concept.
    In only the most pedantic, language specific way possible. Which is what you complained about before. You still haven't explained how it is functionally different. Let me translate your example to C++:

    A declaration of a variable goes like const B a = c;, where a is name, B is type, c is value (usually B can be inferred from c), and mutconst is or isn't there (determining whether a is mutable or not). Variable a has the exact same type as value c (and I mean it in the most literal way possiblesave for the cv-qualifier, but in practical use it's the same). Whether c is mutable or not is irrelevant because the mutability is defined by mutconst, not by type.

    So different! And yes, the cv-qualifier thing is there basically for overload resolution and template instantiation. If your language has neither, then it makes sense that they don't want to call it the same. Quick question, does Rust let you define functions that mutate their arguments? How do you declare that you want to mutate the parameter?

    @LB_ said:

    No, it's actually subtly different. I haven't decided which I prefer, but I like both.
    What's the actual difference?

    @LB_ said:

    Undefined behavior is not a valid use!
    There was no undefined behavior there. If the non-const overload of get_thingie was chosen, it means the object must not have been declared const. Which means that any non-const members of the object are likewise non-const. So casting away the constness of a reference to a member is ok (as I learned today a few posts up!) The cast to const my_class& is necessary to force the const overload to be called, but is always perfectly safe.

    This is only UB if the thing you are returning is a const member, in which case you would just remove the outer const cast.



  • @Kian said:

    What's the actual difference?

    @Gaska already explained the difference: you can instantiate a template twice with the same type if one instantiation uses const. C++ applies const to the type, whereas Rust applies mut to the variable. You can't have a mut type in Rust. Same goes for volatile and const volatile.

    @Kian said:

    There was no undefined behavior there.
    Ok, it still sets off alarm bells though.



  • @LB_ said:

    C++ applies const to the type, whereas Rust applies mut to the variable.

    Bull. I looked it up, here's how you call a function that mutates it's argument in Rust:

    fn add_one(x: &mut i32) {
      *x += 1;
    }
    
    fn main() {
      let mut x = 41;
    
      add_one(&mut x);
    
      println!("{}", x);
    }
    

    See how the mut is on the side of the variable type, next to the i32? You can call it whatever you want, but that looks a lot like mutability being part of the type. Which of course it is, otherwise you can't have static typing enforce const correctness.

    But ok, let's say it's still not part of the type. Let's talk generics. This is how Rust defines a generic function:

    fn takes_anything<T>(x: T) {
      // do something with x
    }
    

    Same exercise as above. How do you write a generic function that modifies its argument? Where do you stick the mut?



  • @Kian said:

    See how the mut is on the side of the variable type, next to the i32?

    Don't let looks deceive you. The mutability and reference-ness is applied to the parameter, not to the type. I don't know enough about Rust to explain or demonstrate it in a better way though, maybe @Gaska can help.


  • Banned

    @LB_ said:

    Uniquely identifying?

    And for that, you don't need to know anything about memory.

    @LB_ said:

    Careful - the fact that references can occupy memory in some cases is an implementation detail.

    I'm pretty sure there exists at least a single code pattern in which GCC can get rid of pointer to local variable in the same scope by writing down all pointer accesses as direct accesses to the underlying variable. And until you have run a debugger, you wouldn't even know.

    @LB_ said:

    Undefined behavior is not a valid use!

    Const-casting non-const to const is well-formed.

    @LB_ said:

    I guess what I want is an "all cases" std::string and then maybe a std::cow_string for when the programmer knows better.

    I'm pretty sure that's what Rust does, except everyone tries to stick with &str as much as possible and not modify anything until truly needed. So it's more like general purpose &str, special case Cow<&str> when you need COW, and special case String when you need ownership.

    @LB_ said:

    This is how much of the rest of C++ is designed - e.g. you choose std::vector first and then if you profile your code and find that (amazingly) std::list is faster in that particular case, you can switch.

    Fun fact: you'd be very hard pressed to find even a single benchmark which shows that std::list is better than std::vector on modern CPU. And that's without the requirement of being realistic case.

    @LB_ said:

    So I should never need to serialize possibly cyclic data structures?

    The only reason why you think you need to know about memory to discover cycles in data structures is because you know that a pointer holds a memory address. Think of a pointer as something that points to an instance of some object, and that dependency goes away.

    @LB_ said:

    Sorry, I don't agree: there are some good use cases for using address as object identity.

    I'd rather say that there are cases where you need to know not just that the object is equal to some other, but that it's the same instance. It just so happens that a memory address works great for this purpose, but it's only an implementation detail.

    @Kian said:

    "Must" be COW?

    Must behave like COW.

    @Kian said:

    You can have immutable structures that just "copy on copy".

    COW behavior can be defined such that whenever an object referenced from more than one place is about to be modified, the original object is going to be left untouched. The logical negation of this sentence is that there exists at least one case in which, when an object referenced from more than one place is about to be modified, the object will be modified in-place.

    Immutable data structures don't have modifying operations at all, so there will never be the case where an operation will modify an object. Thus, the sentence above - the negation of our definition of COW behavior - is false for immutable data structures, which means its negation - the negation of the negation of our definition of COW behavior - is true for them. Let's get rid of the double negation: our definition of COW behavior is true for immutable data structures, which means immutable data structures have COW behavior. QED

    @Kian said:

    You're still wrong.

    C++ variables don't need initialization? 0_o

    @Kian said:

    I was pointing out why there are two requirements and why both requirements must be met, by describing situations in which either one or the other isn't met.

    A well-formed code?

    @Kian said:

    You can get memory without calling a constructor, and you can call a constructor without getting memory.

    But we were talking about object's lifetime.

    @Kian said:

    To have an object, you have to do both in order.

    As I've shown, one is optional. Even if by memory allocation, you don't mean actual allocation, but something more general, like "you must have a chunk of memory not occupied by anything else at this particular moment", you're still wrong because I can construct an object in the same place as something else is living right now, and the compiler will give a damn. And if you're going to say "yes, but that would be obviously an ill-formed code", then you're still wrong because the char type allows you to escape the strict aliasing rule. CBA to find the paragraph in N3376 right now, but trust me, it's there.

    @Kian said:

    I was describing what the memory model for C++ is. Of course it's applicable only to C++.

    I recommend you to try out something else for once. It will broaden your world view. Maybe F#? It's quite a nice functional language, and integrates easily with C#.

    @Kian said:

    Because memory access is expensive.

    The first rule of optimization is: Do Not Optimize.

    Knowing the underlying hardware helps you write a more efficient code, but at the same time, by necessity you'll end up with leaky abstractions (very, very, very leaky abstractions). This will inherently lead to less maintainable code, and will hurt you in the long run unless you really need these few cycles. And in 99,999999% of cases you don't.

    @Kian said:

    C++ makes a lot of sacrifices for performance.

    That's why it sucks on high level.

    @Kian said:

    Ignoring the fundamental memory model of the language means all those sacrifices are wasted.

    Maybe they were making the wrong sacrifices? Maybe the sacrifices they made in the 80s aren't worth it anymore 30 years later?

    @Kian said:

    Something as simple as the order in which the members of an object are declared in C++ can alter the size of the object, as the language mandates padding where the alignment and size of members creates gaps. If you can squeeze more objects per cache line by eliminating wasteful padding, you get better performance. So there's a material gain to understanding the memory model.

    Whereas in other languages, the order of members in memory is a secret implementation detail of a compiler strongly protected from the programmer, which allows the compiler to reorder them freely, which allows to squeeze every bit of storage space possible regardless of visual taste of the programmer.

    @Kian said:

    Then you get into concurrent programming, and the correctness of your program relies on your understanding of the model.

    No, it relies on your understanding of mutexes and other synchronization primitives.

    @Kian said:

    In only the most pedantic, language specific way possible.

    Well, you were asking specifically about Rust - what should I have answered with if not informations specific to Rust? And type systems are all inherently tied to particular languages, and I don't know enough type theory to go meta on this.

    @Kian said:

    You still haven't explained how it is functionally different.

    C++: A a; B b = a; a = b; - one default constructor, one copy constructor, one assignment operator, two destructors.
    Rust: let mut a = A::new(); let b = a; a = b; - one "default constructor", one destructor.

    Rust variables aren't memory locations. Values are. Values don't have mutability. Variables do. You can assign without conversion from mutable to immutable variable just fine. In C++, there's implicit conversion going on - there's entire paragraph in C++ spec dedicated to that. Not to mention that in C++, constructors and destructors would fire away.

    @Kian said:

    Variable a has the exact same type as value c (save for the cv-qualifier, but in practical use it's the same).

    Unless you develop a template-based generic library, where it matters very, very much if something is const or not.

    @Kian said:

    Whether c is mutable or not is irrelevant because the mutability is defined by const, not by type.

    Constness isn't defined by type even though cv-qualifiers are part of type? Ooookay...

    @Kian said:

    And yes, the cv-qualifier thing is there basically for overload resolution and template instantiation

    C has no overloading and no templates, and yet it has cv-qualifiers.

    @Kian said:

    Quick question, does Rust let you define functions that mutate their arguments?

    Yes. The syntax for function arguments is exactly the same as syntax for local variables and for match arms. It's nice to be able to destructure tuples directly in argument list.

    @Kian said:

    This is only UB if the thing you are returning is a const member, in which case you would just remove the outer const cast.

    Actually, it wouldn't be UB either as long as the object the method was called on isn't const either. C++ woes!

    @Kian said:

    See how the mut is on the side of the variable type, next to the i32?

    How many times do I have to repeat that I'm not talking about references when I'm saying mut isn't part of type until you understand that I'm not talking about references when I'm saying mut isn't part of type!? Are you obtuse on purpose or what?

    @Kian said:

    Same exercise as above. How do you write a generic function that modifies its argument? Where do you stick the mut?

    mut x: T



  • @Gaska said:

    The only reason why you think you need to know about memory to discover cycles in data structures is because you know that a pointer holds a memory address. Think of a pointer as something that points to an instance of some object, and that dependency goes away.

    What property of pointers allows me to compare them for equality?


  • Banned

    Memory address, perfect hash, HWND, index in The Great Array Of Everything, serialized string representation... Does it matter?



  • All those things are what I call an address. The identity of an object is its address.


  • Discourse touched me in a no-no place

    @LB_ said:

    I guess what I want is an "all cases" std::string and then maybe a std::cow_string for when the programmer knows better.

    std::cow_string?

    (Thankfully there's some SFW things that can be found when GIS for that. And doubly thankfully I'm at home!)

    But seriously? The history of computing indicates that most programmers are really shitty. We might wish otherwise. We might also wish to be paid billions for no effort at all. Wishing doesn't help. 😄 Because so many programmers are shitty, the non-shitty programmers who design languages need to be extra careful with features: anything that's very difficult to use correctly is an invitation to problems. We don't usually make programming languages that are optimised to make the very smartest people most productive, because that has such an impact on everyone else: that's the Lisp Mistake.

    COW semantics don't work with the sort of threading models that are prevalent in C++. They can be tamed, sure, but the cost of taming is quite high. (Java and C# have entirely different types of runtimes, and so can use different techniques for taming things. Pervasive GC is a gigantic game-changer.) Mutability and threads always cause headaches when brought together…



  • Well I mean, this sort of thing is already in several languages: which container do you choose for a list/set/array of items? And people find cases where they need to roll their own optimized version anyway. You might as well provide the "all cases" and "most cases" versions out of the box.



  • @Gaska said:

    Must behave like COW.

    🐄


  • Banned

    It's especially funny in Rust, because the COW-enabled stdlib type is literally Cow (capitalization and all), and both string types have a method called "into_cow".



  • @Gaska said:

    into_cow

    I much prefer putting cow into me.


  • Banned

    @LB_ said:

    All those things are what I call an address. The identity of an object is its address.

    And that's cool. But my critique was specifically about treating memory address as the only acceptable address. To be more precise - about treating objects as something that's in memory.

    That reminds me - @Kian (and whoever else is interested) - correct me if I'm wrong, but IIRC the C++ standard never says that an object has to be stored somewhere. I think it only says that each lvalue must have unique address, that no two lvalues can overlap in memory (unless at least one of them is a char), that sizeof must return at least 1, and propaply several more tiny details that I forgot at the moment - but it never says that the object must physically exist in meemory.


  • Banned

    @HardwareGeek said:

    I much prefer putting cow into me.

    I think at least one Rust developer is of Soviet descent.



  • @Gaska said:

    But my critique was specifically about treating memory address as the only acceptable address.

    Whoops, I thought I already clarified that wasn't what I meant. Sorry.

    @Gaska said:

    but it never says that the object must physically exist in meemory.
    Probably, or pagefiles would be nonstandard.


  • Banned

    Pagefiles are pretty physical, as much as bytes can be.



  • I thought you meant RAM. Where could you possibly store data that isn't physical? Doesn't that break the laws of physics or something?


  • Banned

    @LB_ said:

    Where could you possibly store data that isn't physical?

    Compile-time evaluation comes to mind. Also, empty base classes.



  • Hmm, I don't think I follow...the end result is stored in the executable binary, the actual calculation happens in the compiler's VM, and at the end of the day all those things are eventually manifested in the physical world. Are we using different definitions of physical?


  • Banned

    These objects don't really exist in runtime, yet they're there in your program nonetheless.



  • We're using different definitions of physical. If they didn't exist at runtime I wouldn't be able to access them.



  • @Gaska said:

    Let's get rid of the double negation: our definition of COW behavior is true for immutable data structures, which means immutable data structures have COW behavior.

    You are confusing "COW behaves like immutable" with "immutable requires COW". COW is an implementation detail. Many different implementations can be used to reproduce immutable behavior. For example, the naive implementation: Copies make independent copies. It may be wasteful, but it models immutable just fine. Since there exists something with a behavior different to copy on write that nonetheless models immutability just as well, your reasoning must be faulty.

    @Gaska said:

    C++ variables don't need initialization? 0_o
    Whether they need initialization is beside the point. Their lifetime can begin before initialization if the constructor is trivial. For example:

    void f() { int x; /* do stuff */ }
    

    x's lifetime starts at the semi colon, and ends at the closing brace. You can take its address or assign to it, and yet it hasn't been initialized. I blame C, where initializing structs by passing pointers to init functions is the common idiom.

    @Gaska said:

    And if you're going to say "yes, but that would be obviously an ill-formed code", then you're still wrong because the char type allows you to escape the strict aliasing rule. CBA to find the paragraph in N3376 right now, but trust me, it's there.
    You can do that because char has a "trivial destructor". If something has a trivial destructor, you can steal its memory at any time. Which is what I said. You're the one that said you could obviate the clarification of "trivial" constructors and destructors.

    @Gaska said:

    The first rule of optimization is: Do Not Optimize.
    While I agree on principle, that's not an excuse for being wasteful. Yes, you shouldn't optimize prematurely, but you also shouldn't go for std::list as your first option.

    @Gaska said:

    No, it relies on your understanding of mutexes and other synchronization primitives.
    Which synchronize memory access: They're part of the memory model. The most complex part, I'd say. But apparently you feel you can jump to complex subjects without learning the basics?

    @Gaska said:

    C++: A a; B b = a; a = b; - one default constructor, one copy constructor, one assignment operator, two destructors.Rust: let mut a = A::new(); let b = a; a = b; - one "default constructor", one destructor
    .
    Ok, so what happens when you actually modify a, which is kind of the point of letting it be mutable?

    @Gaska said:

    And for that, you don't need to know anything about memory.
    So if you don't need to know anything about memory, why is every numeric type defined by their size?

    i8
    i16
    i32
    i64
    u8
    u16
    u32
    u64
    isize
    usize
    f32
    f64

    Also, you are expected to know that char is 4 bytes, considering it's mentioned in the type primer.

    @Gaska said:

    That reminds me - @Kian (and whoever else is interested) - correct me if I'm wrong, but IIRC the C++ standard never says that an object has to be stored somewhere.
    The C++ standard doesn't say that an implementation has to be a computer at all. A person sitting at a table with a lot of paper, a pen and good attention to detail could be a conforming implementation.

    The C++ standard describes the behavior of an abstract machine. An implementation is anything that emulates the visible side effects of that abstract machine.



  • @Kian said:

    A person sitting at a table with a lot of paper, a pen and good attention to detail could be a conforming implementation.

    But not for long, as psychosis will develop quickly.


  • Discourse touched me in a no-no place

    @Kian said:

    A person sitting at a table with a lot of paper, a pen and good attention to detail could be a conforming implementation.

    The amount of paper needed depends on the size of one's handwriting. I write pretty small. 😄

    In fact, I learned Standard ML exactly this way over summer back in 1992; didn't have a laptop, nor could many laptops of the time run the language implementation. It was all pretty easy, except for floating point code. Appreciate what FP hardware does for you; it is non-trivial!



  • @LB_ said:

    Python [...]. Those languages have nightmare type systems.

    What is your beef with Python's type system? For a non-statically typed language it seems to work quite well to me...


  • Banned

    @Kian said:

    You are confusing "COW behaves like immutable" with "immutable requires COW".

    Actually, what I'm claiming is neither one nor the other - I said that immutable behaves like COW, not the other way around.

    @Kian said:

    Whether they need initialization is beside the point. Their lifetime can begin before initialization if the constructor is trivial.

    OK. So let's agree that neither allocation nor initialization is needed for a variable in C++, except when this variable is complex object, in which case initialization (at least a no-op one) is indeed required.

    @Kian said:

    You can do that because char has a "trivial destructor".

    No. You can do that because char is said to be able to do that. Trying to do this with short, unsigned short, int, unsigned int, long int, unsigned long int, long long int, unsigned long long int, float, double or long double - all of these would violate strict aliasing rule and lead to ill-formed code, possibly with UB. But char is OK.

    @Kian said:

    While I agree on principle, that's not an excuse for being wasteful.

    Being wasteful is fine if it makes the code more maintainable and the performance is still acceptable.

    @Kian said:

    Which synchronize memory access

    Only if you think in half-assed abstractions. If you think about code in terms of the code, and not in terms of what it compiles to, then synchronization primitives synchronize variable accesses. And if you throw away all abstractions and see what they really do, then they synchronize the code that is run in multi-threaded situation, which might or might not be because of accessing shared memory, but that's irrelevant from the point of view of the operating system (which provides these primitives).

    @Kian said:

    They're part of the memory model.

    They're part of the memory model only if you incorporate them into it. At their basis, they're just a way to say that some parts of code cannot execute in the same time. I can use mutexes without any shared memory, and they will serve their purpose - it'll just be a different purpose than they're usually used for.

    @Kian said:

    Ok, so what happens when you actually modify a, which is kind of the point of letting it be mutable?

    I did modify it. I assigned variable b to variable a.

    @Kian said:

    So if you don't need to know anything about memory, why is every numeric type defined by their size?

    Rust is system programming language. It leaks some abstraction for the benefit of more control over software's behavior.

    Besides, if you didn't know what these numbers next to type names mean, would it affect you in any way?

    @Kian said:

    Also, you are expected to know that char is 4 bytes, considering it's mentioned in the type primer.

    Because whoever wrote the primer isn't good with abstractions. Rust standard library API reference makes no such mistake.

    @Kian said:

    The C++ standard doesn't say that an implementation has to be a computer at all. A person sitting at a table with a lot of paper, a pen and good attention to detail could be a conforming implementation.

    But it would be a freestanding implementation, which lifts the requirement of having standard library. That's kinda cheating.


  • Discourse touched me in a no-no place

    @ixvedeusi said:

    What is your beef with Python's type system?

    Some people don't understand the difference between putting types on values and putting types on variables.


  • Discourse touched me in a no-no place

    @Gaska said:

    Rust standard library API reference makes no such mistake.

    QFDoingItRight



  • @LB_ said:

    The identity of an object is its address

    What about objects which are completely optimized away by the compiler? Don't they have an identity?



  • @Kian said:

    A person sitting at a table with a lot of paper, a pen and good attention to detail could be a conforming implementation.

    There's also things like Catapult C, though I'm not sure how far these conform to the standard.



  • Presumably the same as in the op:

    class Object(object):
        pass
    
    a = Object()
    a.somefield = some value
    
    

    That is not strong typing.



  • @Buddy said:

    That is not strong typing.

    Don't really see what that has to do with types. It's part of the default interface of custom types that you can add attributes later. If you don't want to allow this, explicitly define the attributes of your custom type with __slots__



  • Look, you asked what the problem was, I pointed you at the post where it was clearly laid out. There is a strong argument for why the syntax for defining a variable or property should be different than accessing or assigning to it. It is made in the original post of this thread. I agree with it.

    The fact that python provides a workaround for the issue doesn't make it a good design decision.



  • Ah, I was thinking you were talking about the type system.

    I can see your point about variable / property definitions, although I'm not sure I agree; IMHO both ways have their place and usefulness, and their problems.



  • @Gaska said:

    So let's agree that neither allocation nor initialization is needed for a variable in C++

    Wait, did you mean "allocation" as calling malloc (or new)? Because that's not how I use it. int x; allocates 4 bytes (in most current consumer architectures) for x. That's why you can take its address. Without allocating space for it, you wouldn't be able to get its address, and so the variable wouldn't exist.

    @Gaska said:

    Besides, if you didn't know what these numbers next to type names mean, would it affect you in any way?
    It would make overflow more likely. "Why doesn't 1000000 fit into u16?" Without understanding that the 16 stands for 16 bits, the limit of 64k doesn't make sense. It might make someone wonder "Why do I have 4 number types? Which one should I use in each situation?"

    @ixvedeusi said:

    What about objects which are completely optimized away by the compiler? Don't they have an identity?
    The compiler outputs code that is supposed to reproduce the visible side effects from running the C++ code in the abstract machine the standard specifies. It's not required to simulate the abstract machine. If the fact that a given variable exists doesn't have visible side effects, it's not required that the compiled program assign memory for that variable. Which is why an optimizer can exist in the first place. If the compiler were expected to simulate the abstract machine, you couldn't omit any steps. However, the compiler is allowed to convert the following program:

    int main() {
      int f = 0;
      for (int i = 0; i < 10000; i++) {
        int g = i + f;
        if (g % 2) f += 10;
      }
      return 0;
    }
    

    into something that just returns without doing anything, because that program doesn't produce any side effects: https://goo.gl/TK1kCG Switch the O flag from O2 to O0 to see how the compiler analyzes the code, sees it doesn't do anything, and just returns immediately.


  • Discourse touched me in a no-no place

    @ixvedeusi said:

    What about objects which are completely optimized away by the compiler? Don't they have an identity?

    If the compiler has managed to work out that an object's identity is unobservable (or at least unobserved), then it need not have an identity. It's up to the compiler/optimizer to prove that though.


  • Banned

    @Kian said:

    Wait, did you mean "allocation" as calling malloc (or new)?

    No, I meant any way to gather memory dedicated to this particular variable.

    @Kian said:

    It would make overflow more likely.

    When was the last time you encountered overflow in practice?

    @Kian said:

    "Why doesn't 1000000 fit into u16?"

    Because 1000000 is more than u16::max_value().

    @Kian said:

    Without understanding that the 16 stands for 16 bits, the limit of 64k doesn't make sense.

    Without understanding the way car engines work, the gasoline/diesel separation at gas station makes no sense. Does it prevent one from driving a car and filling the tank with correct fuel?

    @Kian said:

    It might make someone wonder "Why do I have 4 number types? Which one should I use in each situation?"

    Make them use i64 everywhere.



  • @Gaska said:

    No, I meant any way to gather memory dedicated to this particular variable.

    So, let me see if I understand. Before you used the example:
    let mut a = A::new(); let b = a; a = b;

    Are you saying that because the value of the variables is the same, Rust knows it doesn't need to make a copy? So even though they're different variables, they'll use the same memory for the value?

    So what happens if you ask if a and b are the same variable? Is that even something that makes sense to ask? Can you differentiate identity and value?


  • Banned

    @Kian said:

    Are you saying that because the value of the variables is the same, Rust knows it doesn't need to make a copy?

    Yes, kinda. It's worth noting that it's not just equal value, but literally the same - as in, a single instance of the object that gets reassigned between two variables.

    @Kian said:

    So even though they're different variables, they'll use the same memory for the value?

    Rust variable is not C++ variable. You can't think of Rust variables in terms of C++ variables. In Rust, a variable is just a textual label you can assign to some object so you can reuse it later. The existence of variable doesn't imply there's any memory backing it - the existence of object does, however, but again, it's implementation detail.

    @Kian said:

    So what happens if you ask if a and b are the same variable? Is that even something that makes sense to ask?

    No, because a value can be assigned to at most one variable at a time.

    @Kian said:

    Can you differentiate identity and value?

    Of values? Of course. Of variables? You cannot compare variables.



  • Ok, so lets say I have

    let mut a = A::new(); let b = a; a = b;
    I then do
    let c = b;
    What happens?


Log in to reply