Passing by reference

Continuing @WebReflection's idea from the now-closed ES Discuss mailing list, pass by reference would be a way to accept parts of objects and be able to modify them:

soSomething({a: 123, b: 456})

where

function doSomething({&b}) {
  b = 789 //  we can only modify b (which modifies the b property of the user's passed-in object.
}

The above concept as @WebReflection described is currently similar to:

function doSomething(o) {
  with (o) {
    b = 789 // we can modify b (which modifies the b property of the user's passed-in object).

    // but we can also modify `a`.
  }
}

Andrea mentioned security. If it were to be secure, it would need to be opt-in on the consumer side that passes arguments, not just in the library side:

soSomething(o with b) // or some syntax

Then, if an attacker changes the function header from doSomething({&b}) to doSomething({&a}) there would be a runtime error because the user did not allow a to be referenced.

1 Like

Just to be clear, your example would be the same as this, right?

function doSomething(o) {
  o.b = 789
}

I'm not sure how security is involved then, as this kind of mutability is already possible in Javascript today.

Related proposal: GitHub - rbuckton/proposal-refs: Ref declarations and expressions for ECMAScript

Which I think would look something like this:

function doSomething({ ref b }) {
  b = 789;
}
3 Likes

No, because the part I mentioned above about soSomething(o with b) (or some other syntax) allows the user to opt into defining what is accessible:

Plus, I didn't mention, if an attacker changes the header to doSomething(o) {, that would also trigger the runtime error because it would be like requesting access to all properties (similar to writing something like doSomething({&a, &b, &c, &d, &e, ..., &aa, &ab, ..., &az, &ba, ..., &zz, &aaa, ...etc}) with all the ... parts filled in with the infinte amount of permutations).

Well, here are my thoughts on the matter:

It's generally encouraged to try and keep your data structures immutable. Because of this, I'm not a big fan of any explicit syntax that encourages the opposite behavior (so I'm personally against the linked ref proposal that's currently being considered). Obviously, there are others with a differing viewpoint, otherwise that ref proposal wouldn't be a thing.

As for this concern about attackers, I'm not sure I see it. It's not common to create a program that calls foreign, potentially mallicious functions. I'm not sure there's even a safe way to do that kind of thing. But, assuming it is, and assuming you really need this kind of feature, you can always do something like this:

const myBigObj = { a: 1, b: 2, c: 3, d: 4, e: 5 }

// If you don't want to pass in all of your parameters,
// then don't! Just create a new object with a subset
// of parameters to pass in.
// We seal it to prevent the user from adding new properties.
const subsetOfParams = Object.seal({
  a: myBigObj.a,
  b: myBigObj.b,
  c: myBigObj.c,
})
hostileFunction(subsetOfParams)
// We can apply any modifications to the subsetOfParams obj
// back into the larger object after the function call.
Object.assign(myBigObj, subsetOfParams)

There's other ways to achieve this same kind of API, for example, through getters and setters, or via a proxy.

But in general, this feels like a badly designed API. It would be better if the hostileFunction() returned a new object instead of modifying the passed-in object, and there could potentially be better ways to compose the myBigObj, such that the properties being passed into hostileFunction() are already grouped together in a separate object.

function doSomething({ copy, &ref }) {
  // how do you prevent this?
  arguments[0].copy = "MWAHAHAHA!";
  delete arguments[0].anything;
}
1 Like

One thing I'm missing from this thread and the linked proposal: Why would you ever need a reference in JavaScript?

And secondly I'd like to mention that there is already a Reference (temporary value of a property access) in the specification, I would either name this proposal differently, or use the Reference in the same way it is already used, as a wrapper for an object and a property in it.

If you're writing a realtime game or 3D engine, etc, it is generally encouraged to keep all your objects mutable and the same, and to modify them in place to avoid memory growth and GC.

You don't have to be a fan, but we should fairly recognize that the other side is very valid, and we shouldn't even waste time talking about this.

It's more about developer contracts (f.e. privacy). As an alternative, we could write this following code using lodash to send only what is needed:

someFunc(_.pick(o, ['foo', 'bar']))

But it requires a runtime implementation / third party lib, and syntax is not as nice.

Exactly, we can do that. That's why this ask is for something simpler in form of syntax. :)

Speaking idealistically is not realistic: you can't tell everyone who supplies you an API to write their APIs a certain way.

Maybe if the syntax us being used, the arguments cannot be used? It needs some idea to be proposed and accepted.

Maybe the naming is bad. Just look at what is actually presented (instead of thinking in C/C++ terms?). The naming can be bike shedded.

I agree about the naming confusion. I simply copied the naming from the original proposal, but i thought the described behavior was interesting.

I agree that when you're writing more performant code, then you have to make readability sacrifices in order to improve performance. That's always been the case. And I also recognize that immutability is usually one of the first things to go, because there's a lot of overhead to that. Still, where possible and feasible it's good to try and keep things immutable, and it's good to try and avoid encouraging the practice of mutating data. But, perhaps it's because of users who are in the high-performance situations that the pass-by-reference proposal exists in the first place. We just need to keep that in mind - that these use case of such a proposal is for high-performant situations, not everyday user-clicked-button-on-website-so-I-call-function-to-send-form-request situations that don't need to be optimized the same way.

The other examples I was giving was under the impression that you were trying to protect against malicious actors, so I was trying to demonstrate how that could be done under such rare circumstances, but you've clarified that you were talking simply about code contracts, not about executing code that's intentionally trying to be evil.

1 Like

This is exactly it. In ordinary application code, it's only useful in niche situations, but in perf-sensitive code, it shows its utility a lot more.

I know this thought is really late, but isn't there a fairly simple notion that can encapsulate both the "pass by reference" and the "immutability" concerns at the same time? Here's what occurred to me while I was reading.

Problem 1: There's no easy and concise way to extract object properties into another object.
Problem 2: Creating simple accessors is a pain.

Is the following syntax feasible for this:

let obj = {a: "a", b: "b", c: "c"};
let o = {a: alpha, b: &beta, c: gamma} from obj;

The from keyword would perform the same destructuring we already have, followed by using the values to create a new object. The & on the beta name would generate a simple accessor for the beta property of the new object instead of a normal property with the destructured value of b. This is the equivalent code

let o = Object.create(Object.prototype, {
  alpha: {
    enumerable: true,
    configurable: true,
    writable: true,
    value: obj.a
  },
  beta: {
    enumerable: true,
    configurable: true,
    get() { return obj.b; },
    set(v) { obj.b = v }
  },
  gamma: {
    enumerable: true,
    configurable: true,
    writable: true,
    value: obj.c
  }
});

Internally, the beta accessor could easily be optimized away into a direct reference to obj.b. That's up to the engine developers to decide.

What this gives us is not only the means to concisely extract a subset of an object into another object, but also the possibility of doing so in a way that allows only a subset of an object to be made accessible to a function. This also keeps control of what a function receives in the hands of the caller, which should reduce any security concerns.

I did consider what would happen if this notation were applied function declarations as a parameter, but I considered this non-viable as it would mean still exposing the entire object definition to the function as it was passed in, not to mention the loss of immediately destructured parameters when from is used.

1 Like

It would be so awesome to have that syntax @rdking, as a replacement of lodash _.pick which is so convenient. Very interesting how it allows the possibility of making one property shared between the source and target objects.