How to use storage class specifiers such as ref, in, out, etc. In function arguments in D?

There are relatively many storage class specifications for function arguments in D that:

  • no one
  • in (which is equivalent to const scope )
  • out
  • ref
  • scope
  • lazy
  • const
  • immutable
  • shared
  • inout

What is reasonable behind? Their names have already given obvious use. However, there are some open questions:

  • Should I use ref in combination with in for struct arguments to a type function by default?
  • Does out mean ref ?
  • When should I use it?
  • Does ref make sense in classes and / or interfaces? (By default, class types are references.)
  • How about ref on array slices?
  • Should I use const for built-in arithmetic types when possible?

In the general case: when and why should I use the storage class specifier for function argument types in the case of built-in types, arrays, structures, classes and interfaces? (To highlight the scope of the issue, do not discuss shared , as it has its own isolated meaning.)

+4
source share
1 answer
  • I would not use the default. The ref parameters accept only lvalues, which means that you are going to change the passed argument. If you want to avoid copying, use const ref or auto ref . But const ref still requires an lvalue, so if you don't want to duplicate your functions, this is often more annoying than worth it. And while auto ref will avoid copying lvalues ​​(this basically makes it such that there is a version of a function that takes lvalues ​​to ref and one that accepts rvalues ​​without ref ), it only works with templates, limiting it utility, And using const can have far-reaching consequences due to the fact that D const transitive and the fact that undefined behavior discards const from a variable and modifies it. Thus, although this is often useful, using the default is likely to lead to trouble.

    Using in gives you scope in addition to const , which I usually advised about. scope in terms of functional parameters is supposed to be such that no reference to this data can escape the function, but the checks for it have not yet been performed properly, so you can use it in a lot more situations than was supposed to be legal. There are cases where scope invaluable (for example, with delegates, since it does so that the compiler does not need to allocate a closure for it), but for other types it can be annoying (for example, if you pass array be scope , then you cannot return slice from this array from this function). And any structures with any arrays or reference types will be affected. And although you will not have many complaints about the misuse of scope right now, if you use it everywhere, you will definitely get many errors after fixing it. In addition, it is completely pointless for value types, since they have no escape references. Thus, the use of const and in for a value type (including structures that are value types) are virtually identical.

  • out same as ref , except that it resets the parameter to the init value so that you always get the same value, no matter what the previous state of the passed variable was.

  • Almost always, as far as function arguments go. You use const or scope or something else when you have a specific need, but I would not recommend using them by default.

  • Sure. ref is different from the concept of class references. This is a reference to the passed variable. If i do

     void func(ref MyClass obj) { obj = new MyClass(7); } auto var = new MyClass(5); func(var); 

    then var will refer to the newly built new MyClass(7) after calling func , and not to new MyClass(5) . You are passing the ref link. This is exactly the same as selecting a link address (e.g. var ) gives you a pointer to a link, not a pointer to a class object.

     MyClass* p = &var; //points to var, _not_ to the object that var refers to. 
  • Same as with classes. ref forces the parameter to refer to the variable passed to. For instance,

     void func(ref int[] arr) { arr ~= 5; } auto var = [1, 2, 3]; func(var); assert(var == [1, 2, 3, 5]); 

    If func did not accept its ref argument, then var would be cut off, and adding to arr would not affect var . But since the parameter was ref , everything that was done for arr is done with var .

  • It is completely up to you. To do this, const makes it impossible for you to mutate it, which means that you are protected from accidentally mutating it if you are not going to mutate it. It may also allow some optimizations, but if you will never write to a variable, and this is a built-in arithmetic type, then the compiler knows that it has never changed, and the optimizer should be able to do these optimizations anyway (although regardless independent of compiler implementation).

    immutable and const almost identical for built-in arithmetic types in almost all cases, so personally I just used immutable if I want to guarantee that such a variable does not change. In general, using immutable instead of const , if you can give you better optimization and better guarantees, since it allows you to implicitly share a variable between threads (if applicable) and always ensures that a variable cannot be changed (whereas for reference types, const means only that this link cannot mutate the object, and not that it cannot be mutated).

    Of course, if you mark const and immutable variables as much as possible, then it really helps the compiler to optimize at least part of the time, and it makes it easier to detect errors where you mutated something when you didn't want it. It can also make your code more understandable, as you know that the variable will not be mutated. Thus, using them liberally can be valuable. But then again, using const or immutable can be overly restrictive depending on the type (although this is not a problem using the built-in integral types), so just automatically tagging everything as const or immutable can cause problems.

+4
source