Hacker Newsnew | past | comments | ask | show | jobs | submit | zuzuleinen's commentslogin

SEEKING WORK | Remote Location: Romania

Technologies: Go, Javascript, React.js, PHP, Laravel, Symfony, MySQL, Postgres, AWS, Kubernetes

Résumé/CV: https://www.linkedin.com/in/andrei-boar-7aa32ab7

Email: andrey.boar at gmail com


SEEKING WORK | Remote

Location: Romania

Technologies: Go, Javascript, React.js, PHP, Laravel, Symfony, MySQL, Postgres, AWS, Kubernetes

Résumé/CV: https://www.linkedin.com/in/andrei-boar-7aa32ab7

Email: andrey.boar at gmail com


Location: Romania

Remote: Yes

Willing to relocate: No

Technologies: Go, Javascript, React.js, PHP, Laravel, Symfony, MySQL, Postgres, AWS, Kubernetes

Résumé/CV: https://www.linkedin.com/in/andrei-boar-7aa32ab7

Email: andrey.boar at gmail com


I like to have an Obsidian vault for every company I work for.

But when I really need to think I upgrade to pen and paper.


Is this related to csprimer.com?


The LinkedIn on this one links to Arjit Sharma, not Oz Nova of csprimer.com.


I think https://csprimer.com/courses/ is a good fit if you also want community. I think Oz Nova who is behind csprimer was one of the authors of teach yourselfcs


I’ve been looking closely at this too. $75/month isn’t bad all things considered.

I’d love to hear more personal anecdotes from people who are active in CSPrimer. Downsides I could see (not having joined):

- Some parts of CSPrimer are not finished yet.

- How active is the community really? It’s hard to tell without joining and seeing for yourself.

- How much access to Oz do you really get (same as above)? - How much mentorship do you really get (again same as above).


He is pretty active and holds regular Q&A sessions.


I highly recommend csprimer for this.

https://csprimer.com/

Or if you want to just use books go with:

https://teachyourselfcs.com/


How did you got tested for your stomach bug?


I didn't. But the there was one person on TV that casually made a comment about his unexpected weight lost and that he did visit a doctor and that's what it was. For me the explanation hit so close home that I immediately audited all I ate and drank and took action. Then that left me with gastritis which made me feel about the same and only slightly better. I search fir gastritis diets and followed that about 6 days. I also took an antiacid when I felt bad. After that I realized that I need to eat more food, because I was a little more active but didn't have enough energy. I learned to avoid the food that made me sick and I needed to unlearn this: carbs, beans, meat, milk. If you become sick enough, all food feels like poison. Then I realized I was missing probably all the vitamins, that's what it means you don't absorb the food. I felt good after some multivitamins, some magnesium and really good after Vit. C: higher appetite, less muscle and tendons pain, more energy. Now I'm at the point when I can go to the gym again and not feel like I need to sleep right now after 3 squats.


To generalize the title into a rule is good to remember that in Go everything is passed by value(copy).


That is the case for almost every modern language. C++ is one of the few languages that has "references" and at least last I looked that's a language accommodation over what are pointers being passed by value in the assembly, at least until compiler optimizations take over (and that's not limited to references either).

If you're in 2024 and you're in some programming class making a big deal about pass-by-value versus pass-by-reference, ask for your money back and find a course based in this century. Almost literally any topic is a better use of valuable class time than that. From what I've seen of the few unfortunate souls suffering through such a curriculum in recent times is that it literally anti-educates them.


For a last-century example of actual pass-by-reference, assign-by-copy, the PL/I program

  foo: proc options(main);
    dcl sysprint print;
    dcl a(3) fixed bin(31) init(1,2,3);
    put skip data (a);
    call bar(a);
    put skip data (a);
  
  bar: proc(x);
    dcl x(3) fixed bin(31);
    dcl b(3) fixed bin(31) init(3,2,1);
    x = b;
    b(1) = 42;
    x(2) = 42;
    put skip data (b);
    put skip data (x);
  end bar;
  
  end foo;
outputs

  A(1)=   1   A(2)=   2   A(3)=   3 ;
  B(1)=  42   B(2)=   2   B(3)=   1 ;
  X(1)=   3   X(2)=  42   X(3)=   1 ;
  A(1)=   3   A(2)=  42   A(3)=   1 ;
demonstrating that X refers to A in BAR and assigning B to X copies B into X (= A).


Sure, if they want to be bad developers in C#, F#, Swift, D, Rust, Ada, VB, Delphi, just to stay on the ones that are kind of relevant in 2024 for business, for various level of relevant, versus the ones story has forgotten about.


Rust doesn't have pass-by-reference in the sense that C# or C++ do. The `&` and `&mut` types are called references[1], but what is meant by "reference" is the C++ guarantee that a reference always points to a valid value. This C++ code uses references without explicit pointer deferencing:

    #include <iostream>
    #include <string>
    
    void s_ref_modify(std::string& s)
    {
        s = std::string("Best");
        return;
    }
    
    int main()
    {
        std::string str = "Test";
        s_ref_modify(str);
        std::cout << str << '\n';
    }
The equivalent Rust requires passing a mutable pointer to the callee, where it is explicitly dereferenced:

    fn s_ref_modify(s: &mut String) {
        *s = String::from("Best");
    }
    
    fn main() {
        let mut str = String::from("Test");
        s_ref_modify(&mut str);
        println!("{}", str);
    }
Swift has `inout` parameters, which superficially look similar to pass-by-reference, except that the semantics are actually copy-in copy-out[2]:

> In-out parameters are passed as follows:

> 1. When the function is called, the value of the argument is copied.

> 2. In the body of the function, the copy is modified.

> 3. When the function returns, the copy’s value is assigned to the original argument.

> This behavior is known as _copy-in copy-out_ or _call by value result_. For example, when a computed property or a property with observers is passed as an in-out parameter, its getter is called as part of the function call and its setter is called as part of the function return.

[1]: https://doc.rust-lang.org/book/ch04-02-references-and-borrow...

[2]: https://docs.swift.org/swift-book/documentation/the-swift-pr....


Is copying huge blocks of data free in 2024? My benchmarks suggest otherwise, and the world still needs assembly programmers.


The way almost all programming languages work is that they explicitly pass a copy of a pointer to a function. That is, in almost all languages used today, whether GC or not, assigning to a function parameter doesn't modify the original variable in the calling function. Assigning to a field of that parameter will often modify the field of the caller's local variable, though.

That is, in code like this:

  ReferenceType a = {myField: 1}
  foo(a)
  print(a.myField)
  
  void foo(ReferenceType a) {
    a.myField = 9
    a = null
  } 
Whether you translate this pseudocode to Python, Java, C# (with `class RefType`), C (`RefType = *StructType`), Go (same as C), C++ (same as C), Rust, Zig etc - the result is the same: the print will work and it will say 9.

The only exceptions where the print would fail with a null pointer issue that I know of are C++'s references and C#' s ref parameters. Are there any others?


Right. Passing pointers is much cheaper than passing values of large structures. And then references are an abstraction over pointers that allow further compile-time optimization in languages that support it. Pass-by-value, pass-by-pointer, and pass-by-reference are three distinct operational concepts that should be taught to programmers.


I think the right mental model is pass-by-value for the first two. There is nothing different in the calling convention between sending a parameter of type int* vs a parameter of type int. They are both pass-by-value. The value of a pointer happens to be a reference to an object, while the value of an int is an int. In both cases, the semantics is that the value of the expression passed to the function is copied to a local variable in the function.

Depending on the language, that is very likely the whole picture of how function calls work. In a rare few modern languages, this is not true: in C# and C++, when you have a reference parameter, things get sonewhat more complicated. When you pass an expression to a reference parameter, instead of copying the value of evaluating that expression into the parameter of the function, the parameter is that value itself. It's probably easier to explain this as passing a pointer to the result of the expression + some extra syntax to auto-dereference the pointer.


> I think the right mental model is pass-by-value for the first two. There is nothing different in the calling convention between sending a parameter of type int* vs a parameter of type int.

You're talking about parameters of type int; I'm talking about structs that are strictly larger than pointers. Structs which may be nested; for which deep copies are necessary to avoid memory leaks / corruption. And here, the distinction between these "mental models" exhibits a massive gap in real performance.

Here's a deliberately pathological case in C++; I've seen this error countless times from programmers in languages that make a distinction between references/pointers and values:

    bool vector_compare(vector<int> vec, size_t i, size_t j) {
        return vec[i] < vec[j];
    }

    int vector_argmin(vector<int> vec) {
        if (vec.size()) {
            size_t arg = 0;
            for(size_t i = 1; i < vec.size(); i++) {
                if (vector_compare(vec, i, arg))
                    arg = i;
            }
            return arg;
        } else return -1;
    }
The vector_compare function makes a copy of the full vector before doing its thing; this ends up turning my linear-looking runtime into accidentally-quadratic. From the perspective of this solitary example, it would make sense to collapse reference/pointer into the same category and leave "value" on its own.

But actually these are three distinct concepts, with nuance and overlap, that should be taught to anybody with more than a passing interest in languages and compilers. I'm not here to weigh in on what constitutes a modern language, but the notion that we should just throw this crucial distinction away because some half-rate programmers don't understand it is patently offensive.


My point is the same for int as for vector<int>. There is 0 difference in the C++ calling convention between passing a vector<int> and a vector<int>: they both copy an object of the parameter type. Of course, copying a 1000 element vector is much slower than copying a single pointer, but the difference is strictly the size of the type. The copying occurs the same way regardless. This is also the reason foo(char) is less overhead than a foo(char).

Everything (except reference types) is pass-by-value, but of course values can have wildly different sizes.

Also, the problem of accidentally copying large structs is not limited to arguments, the same considerations are important for assignments. Another reason why "pass-by-pointer" shouldn't be presented as some special thing, it's just passing a pointer copy.


Your point rather misses the mark.

Your vector<int*> is a red herring. The distinction I'm making is between passing a (vector<int>)* and a vector<int>, because those two objects have radically different sizes, and the distinction can and does create severe performance issues. And yet, pointers are still different from references: with a reference, you don't even need your object to have a memory address.


HN markup ate my *... Yes, I'm also talking about vector<int> and vector<int>*. They are indeed of radically different sizes, and the consequences of copying one are very different from the consequences of copying the other.

But this doesn't change the fact that they are both passed-by-value when you call a function of that parameter type.


It’s semantics only. The compiler is free to optimize it in any way, e.g. if a function call gets inlined, there is nothing “returning” to begin with, it’s all just local values.


See cousin posts. That's not what the terms mean.


Both C# and Swift makes a distinct difference by having both struct and classes.


That is not what the pass-by-copy vs. pass-by-reference distinction is. Both are passing by value, one is just a pointer (under the hood) and the other is not. But the incoming parameter is a copy.

See my cousin post: https://news.ycombinator.com/item?id=41220384

This is a distinction so dead that people basically assume that this must be talking about whether things are passed by pointer, because in modern languages, what else would it be? But that is not what pass-by-reference versus pass-by-copy means. This is part of why it's a good idea to let the terminology just die.


So what should we call "foo(x)" and "foo(ref x)" in C# to distinguish them if not pass-by-value and pass-by-reference?


C# can call it that specifically if it likes, because the general computer science term is dead, but under the hood you're passing a reference by value. Look to the generated assembler in a non-inlined function. You'll find a copy of a pointer. You did not in true pass-by-refernce langauges.

The fact that is a sensible thing to say in a modern language is another sign the terminology is dead.


C#'s ref parameters, same as C++' s reference types, have true pass-by-reference semantics. Whether this gets compiled to pass-by-pointer or not is not observable from the language semantics.

That is, the following holds true:

  int a = 10;
  foo(ref a) ;
  Assert(a == 100);

  void foo(ref int a) 
  {
    a = 100;
  }
There's also a good chance that in this very simple case that the compiler will inline foo, so that it will not ever pass the address of a even at the assembly level. The same would be true in C++ with `void foo (int& a)`.


Right, and on the flip side, Forth on say x86 will inevitably involve passing a pointer to the stack, be it explicitly or implicitly (ie global variable).

So if one invokes low-level implementation details, Forth is also a pass-by-pointer-value in the same way as C# "ref" and others, at least on x86.

However I don't think appealing to implementation details is useful, what matters is what is observed through the language, with the results you point out.


You're talking about pointers but calling them references. I'm sorry, but no, the terminology is not "dead" you're just contributing to confusion.


Is python no longer a modern language? Objects are certainly not copied when passed to a function.


Python copies references by value.

    $ python3
    Python 3.12.3 (main, Jul 31 2024, 17:43:48) [GCC 13.2.0] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> def x():
    ...     v = 1
    ...     y(v)
    ...     print(v)
    ... 
    >>> def y(val):
    ...     val += 1
    ... 
    >>> x()
    1
A pass-by-reference language would print 2.

Everything in a modern language is passed by copy. Exactly what is copied varies and can easily be pointers/references. But there were languages once upon a time that didn't work that way. It's a dead distinction now, though, unless you go dig one of them up.

If you want a specific one to look at, look at Forth. Note how when you call a function ("invoke a word", closest equivalent concept), the function/word doesn't get a copy of anything. It directly gets the actual value. There is no new copy, no new memory location, it gets the actual same memory as the caller was using, and not as a "pointer"... directly. Nothing works like that any more.


C++ is a live language, C# has out parameters.... there's stuff out there.

The classic example of "pass by copy-reference is less expressive" is you can't have pass a reference to number and have the caller modify it. You have to explicitly box it. I understand you understand this, but it's worth considering when thinking about whether the distinction means absolutely nothing at all.


> The classic example of "pass by copy-reference is less expressive" is you can't have pass a reference to number and have the caller modify it.

This is really not true. Depending on how your language implements pass-by-reference, you can pass a reference to an int without boxing in one of two ways: either pass a pointer to the stack location where the int is stored (more common today), or simply arrange the stack in such a way that the local int in the caller is at the location of the corresponding parameter in the callee (or in a register).

The second option basically means that the calling convention for reference parameters is different from the calling convention for non-reference parameters, which makes it complicated. It also doesn't work if you're passing a heap variable by reference, you need extra logic to implement that. But, for local variables, it's extremely efficient, no need to do an extra copy or store a pointer at all.


Hmmm... yeah that's a good point. Though I would contend that the fact that languages do not do this is indicative of... something.


I would guess that the main reason is that the on-stack way only works for local variables. If you want to pass anything else by reference, you need to use some kind of address to it, since it's not in the caller's stack anyway.


uh I'd not say it like that

Python passes primitive types by value, out rather "as if by value", because it copies them on write.

if you modify your experiment to pass around a dict or list and modify that in the 'y', you'll see y is happily modified.

so Python passes by reference, however it either blocks updates (tuple) or copies on write (int, str, float) or updates in place (dict, list, class)


> if you modify your experiment to pass around a dict or list and modify that in the 'y', you'll see y is happily modified.

No, you won't.

  x = {'a' : 1}
  foo(x)
  print(x)

  def foo(z):
    z = {'b' : 2}
You'll see that this prints `{'a' : 1}`, not `{'b' : 2}`. Python always uses pass-by-value. It passes a copy of the pointer to a dict/list/etc in this case. Of course, if you modify the fields of the z variable, as in `z['b'] = 2`, you do modify the original object that is referenced by z. But this is not pass-by-reference.


Is it not pass-by-reference by some technicality? In the mutation example you suggest, if a reference to x isn't being passed into foo, how could foo modify x?

I would sooner believe the example is showing you shadowing the z argument to foo, than foo being able to modify the in-parameter sometimes even if it's pass by value.


> In the mutation example you suggest, if a reference to x isn't being passed into foo, how could foo modify x

The important point is that it's not "a reference to x" that gets passed, it's a copy of x's value. x's value, like the value of all Python variables, is a reference to some object. The same thing applies to setting variables in Python in general:

  x = {1:2} # x is a new variable that references some dict 
  y = x # y is a new variable that references the same dict
  y[1] = 7 # the dict referenced by x and y was modified
  x = None # x no longer references the dict
  print(y) # y still references the dict, so this will print {1:7}
  y = None # now neither x nor y reference that dict; since y was the last reference to it, the dict's memory will be freed


This is what confuses me; it sounds like what you're saying is Python isn't pass-by-reference, it's pass-by-value, and that value is sometimes a reference?

Honestly, "x's value, like the value of all Python variables, is a reference to some object" makes me think it's more accurate to call Python pass-by-reference only.


Pass-by-reference means that your callee gets a reference to your local variables, and can modify them. This is impossible in Python. Pass by value means that your callee gets the values of your local variables and can't modify them. This is how Python functions work.

What those values represent and how they can be used is a completely different topic. Take the following code:

  x = "/dirs/sub/file.txt"
  with open(x, "w") as file:
    file.write("abc")
  foo(x)
  with open(x, "r") as file:
    print(file.read_all()) #prints "def" 

  def foo(z):
    with open(z, "w") as file:
      file.write("def")
      
Here x is in essence a "reference to a file". When you pass x to foo, it gets a copy of that reference in z. But both x and z refer to the same file, so when you modify the file, both see the changes. The calling convention is passing a copy of the value to the function. It doesn't care what that value represents.


So to be very clear:

  def foo(x):
    x['a'] = 1
  
  y = {'b': 2}
  foo(y)
  print(y)
foo can modify the object y points to, but it can't make y point to a different object? Is that what "This is impossible in Python" is referring to?


Yes.


yes! we agree how this works.

so we disagree on terminology?

in my CS upbringing, sharing the memory location of a thing as parameter was tagged "call by reference". the hallmark was: you can in theory modify the referenced thing, and you just need to copy the address.

call by value, in contrast, would create an independent clone, such that the called function has no chance to modify the outside value.

now python does fancy things, as we both agree. the result of which is that primitives (int, flot, str) behave as if they were passed by value, while dict and list and its derivatives show call by reference semantics.

I get how that _technically_ sounds like call by value. and indeed there is no assignment dunder. you can't capture reassignment of a name.

but other than that a class parameter _behaves_ like call by reference.


"In the mutation example you suggest, if a reference to x isn't being passed into foo, how could foo modify x?"

Because it is passing a pointer by value under the hood.

This is the part that messes everyone up. Passing pointers by value is not what passing by reference used to mean.

And it matters, precisely because that is extremely realistic Python code that absolutely will mess you up if you don't understand exactly what is going on. You were passed a reference by value. If you go under the hood, you will find it is quite literally being copied and a ref count is being incremented. It's a new reference to the same stuff as the passed-in reference. But if you assign directly to the variable holding that reference, that variable will then be holding the new reference. This is base level, "I'd use it on an interview to see if you really know Python", level stuff.

Everything in a modern language involves passing things by value. Sometimes the language will gloss over it for you, but it's still a gloss. There were languages where things fundamentally, at the deepest level, were not passed by value. They're gone. Passing references by copy is not the same thing, and that Python code is precisely why it's not the same thing.


Sure, but in the traditional sense of pass-by-reference you could say it's just always pass-by-value, and that value is always a reference. It's just not a helpful thing to say. (In those traditional pass by reference languages, was it impossible to pass a scalar to a function?)

Passing a pointer-by-value similarly seems to be avoiding the point; if you tell me Python is always pass-by-value I'll expect an object I pass to a function to be a copy & not a reference, thus not be able to be mutated, and that's not the case.


> Passing a pointer-by-value similarly seems to be avoiding the point; if you tell me Python is always pass-by-value I'll expect an object I pass to a function to be a copy & not a reference, thus not be able to be mutated, and that's not the case.

That would be a misunderstanding. It would only make sense if you think Python variables are Python objects. They are not: Python variables are pointers to objects. The fact that assignment to a variable never modifies the object pointed to by that variable is a consequence of that, and doesn't apply just to passing that variable to a function.


you replace the local binding z to the dict globally bound to x by a local dict in that z = ... assignment.

however if you do z['b'] = 2 in foo, then you'll see the global dict bound to x has been modified, as you have stated.

well, that's _exactly_ pass by reference.


There is no notion of variable binding in Python, that's a different thing. z, like any Python variable, is a reference to something. Initially, it's a reference to the same dictionary that x references. If we modify the object referenced by z (e.g. by adding a new item), we of course also modify the object referenced by x, as they are referencing the same object initially. However, when we assign something to z, we change the object that z is referencing. This has no effect on x, because x was passed-by-value to foo().

Pass-by-reference doesn't exist in Python. Here's what it looks like in C#, which does suport it:

  auto x = Dictionary<string, int >() ;
  x.Add("a", 1);
  foo(ref x);
  System.Println(x); //prints {b: 2}

  void foo(ref Dictionary<string, int> z) {
    auto k = new Dictionary<string, int>();
    k.Add("b", 2);
    z = k;
  }
Here z is just a new name for x. Any change you make to z, including changing its value, applies directly to x itself, not just to the object referenced by x.



But the author already knew that.

The important lesson is that assignments are by value(copy).


Maps and channels and functions are passed by reference. Slices are passed and returned by value but sometimes share state invisibly, the worst of both worlds. It would make more sense if Go either made this stuff immutable, made defensive copies, or refused and required using explicit pointers for all these cases.


No, it's not the case and this terminology shouldn't be used as it's confusing and unhelpful.

There are reference types in Go even though this is also not a super popular term. They still follow the pass-by-value semantics, it's just that a pointer is copied. A map is effectively a pointer to hmap data structure.

In the early days of Go, there was an explicit pointer, but then it was changed.

Slices are a 3-word structure internally that includes a pointer to a backing array and this is why it's also a "reference type".

That said, everything is still passed by value and there are no references in Go.


You are splitting hair, Maps are effectively references.

That's like saying C++ doesn't have references since it's just a pointer being copied around


No, there is a real difference, this is not splitting hairs.

Go is always pass-by-value, even for maps [0]:

  x := map[int]int{1: 2}
  foo(x)
  fmt.Printf("%+v", x) //prints map[1:2]

  func foo(a map[int]int) {
    a = map[int]int{3: 4}
  }
In contrast, C++ references have different semantics [1]:

  std::map<int, int> x {{1, 2}};
  foo(x);
  std::print("{%d:%d}", x.begin()->first, x.begin()->second);
  //prints {3:4}

  void foo(std::map<int, int>& a) {
    a = std::map<int, int> {{3, 4}}; 
  } 
[0] https://go.dev/play/p/6a6Mz9KdFUh

[1] https://onlinegdb.com/j0U2NYbjL


foo is receiving a mutable reference and it can't modify the map without those changes leaking out permanently to the caller: https://go.dev/play/p/DXchC5Hq8o8. Passing maps by value would have prevented this by copying the contents.

It's a quirk of C++ that reference args can't be replaced but pointer args can.


The point is that the local variable referencing the map is a different entity than the map itself. Foo gets a copy of that local variable, and the copy references the same map object.

And the fact that C++ references can be used for assignment is essentially their whole point, not "a quirk".


Great video!


thank you :)

I'm trying to record my self talking while operating something on my computer, I give up after 20+ retake, and end up pivoting to this kind of video.


Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

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

Search: