guidelines for parameter types

Dan dbdavidson at yahoo.com
Mon Dec 17 12:46:25 PST 2012


Assume V is a non-template parameter type and v is a parameter of 
that type for any function. Also assume T is a template parameter 
type and t is a parameter of that type for any function. Is the 
following table and set of guidelines below reasonable? What 
other guidelines do you use or would make sense to follow? I 
apologize if this is obvious/well known and I consider myself new 
to D, so please address any of my misconceptions. I'm finding 
that on the surface D sounds much simpler than it is, but if I 
can get a good set of guidelines it should all work out.

Thanks,
Dan

(COW means Copy on Write)

| convention              | what it means/when to use             
     |
|-------------------------+-------------------------------------------|
| V v                     | V is primitive, dynamic arr, assoc 
array, |
|                         | COW, or kown copy cheap (< 16 bytes)  
     |
|                         |                                       
     |
| const(V) v              | same as (V v), pedantic - makes copy 
and  |
|                         | guarantees no mutation in function    
     |
|                         |                                       
     |
| immutable(V) v          | No need - for ensuring no local 
changes   |
|                         | prefer 'const(V) v'                   
     |
|                         |                                       
     |
| ref V v                 | Use only when mutation of v is 
required   |
|                         |                                       
     |
| ref const(V) v          | Indicate v will not be changed, 
accepts   |
|                         | {V, const(V), immutable(V)}           
     |
|                         |                                       
     |
| ref immutable(V) v      | No need - restrictive with no benefit 
     |
|                         | over 'ref const(V) v'                 
     |
|                         |                                       
     |
| V* v                    | Use only when mutation of v is 
required.  |
|                         | Prefer 'ref V v' unless null 
significant  |
|                         | or unsafe manipulations desired       
     |
|                         |                                       
     |
| const(V)* v             | Indicate v will not be changed,       
     |
|                         | accepts {V*, const(V)*, 
immutable(V)*}    |
|                         | still prefer ref unless null 
significant  |
|                         | or unsafe manipulations desired       
     |
|                         |                                       
     |
| immutable(V)* v         | No need - restrictive with no benefit 
     |
|                         | over 'const(V)* v'                    
     |
|                         |                                       
     |
| T t                     | T is primitive, dynamic array, or 
assoc   |
|                         | array (i.e. cheap/shallow copies). 
For    |
|                         | generic code no knowledge of COW or   
     |
|                         | cheapness so prefer 'ref T t'         
     |
|                         |                                       
     |
| const(T) t              | same as (T t), pedantic - makes copy 
and  |
|                         | guarantees no mutation in function    
     |
|                         |                                       
     |
| immutable(T) t          | No need - for ensuring no local 
changes   |
|                         | prefer 'const(V) v'                   
     |
|                         |                                       
     |
| ref T t                 | Use only when mutation of t is 
required   |
|                         | prefer 'ref const(T) t' if mutation 
not   |
|                         | required                              
     |
|                         |                                       
     |
| ref const(T) t          | Indicate t will not be changed, 
accepts   |
|                         | {T, const(T), immutable(T)} without 
copy  |
|                         |                                       
     |
| ref immutable(T) t      | No need - restrictive with no benefit 
     |
|                         | over 'ref const(T) t'                 
     |
|                         |                                       
     |
| auto ref T t            | Use only when mutation of t required 
and  |
|                         | want support of by value for rvalues  
     |
|                         | (May be obviated in the long run)     
     |
|                         |                                       
     |
| auto ref const(T) t     | Indicate t will not be changed, 
accepts   |
|                         | [lr]value {T, const(T), immutable(T)} 
     |
|                         | (May be obviated in the long run)     
     |
|                         |                                       
     |
| auto ref immutable(T) t | No need - restrictive with no benefit 
     |
|                         | over 'auto ref const(T) t'            
     |
|                         |                                       
     |
| T* t                    | Use only when mutation of t is 
required.  |
|                         | Prefer 'ref T t' unless null is       
     |
|                         | significant or dealing with unsafe 
code.  |
|                         |                                       
     |
| const(T)* t             | Prefer 'ref const(T) t' unless        
     |
|                         | null is significant or dealing with   
     |
|                         | unsafe code                           
     |
|                         |                                       
     |
| immutable(T)* t         | No need - restrictive with no benefit 
     |
|                         | over 'const(T)* t'                    
     |
|                         |                                       
     |


*** Parameter Type Guidelines ***

  - Use pointers when null has specific intended meaning or the 
function wants unsafe code, otherwise prefer ref

  - Prefer const(T|V) to immutable(T|V) because const(T|V) is 
accepting of mutables and it ensures they are not mutated. 
immutable(T|V), on the other hand, only accepts immutables for 
types with aliasing and therefore makes the function less 
applicable. This eliminates 7 rows from consideration.

  - Always use const(T|V) when passing by ref if referred to 
instance is not mutated. For the non template case (i.e. V) not 
using const(V) means const and immutables can not be used as 
arguments. This unnecessarily reduces the application of the 
function. This is a debatable guideline for template types T, 
since the T being parameterized could be T = const(S), so it does 
not prevent the function from being called with const(S) or 
immutable(S). But the real problem with using just T instead of 
const(T) in the signature of a function that does not mutate t is 
the developer reading the signature has no way of knowing that T 
will not be mutated without compiling and seeing if it breaks. It 
is as if important user information is missing. So 'foo(T)(T t)' 
or 'foo(T)(ref T t)' may both accept 'const(S) s', but only if 
the compiled code does not mutate s. But without showing that 
guarantee to the compiler and developer with signature like 
'foo(T)(const(T) t)' or 'foo(T)(ref const(T) t)' you can be 
setting yourself up for future problems. For example, if you go 
with 'foo(T)(ref T t)', in the future you might (accidentally) 
add a mutating call on t. Then all existing code that passed in 
const(S) would break and if you test with only mutables you might 
not see the errors. An example from phobos that violates this is 
formatValue when passing in a struct. Even though the argument is 
not modified (why would it be) the signature is has 'auto ref T 
val' instead of 'auto ref const(T) val'.

  - Prefer 'ref' to by value on all template parameters that are 
not primitives, dynamic arrays or associative arrays - since 
there is no knowledge of how expensive the copy will be. (this is 
a guideline that is violated by SortedRange.(lowerBound, 
upperBound, trisect)).

  - When to use 'auto ref' template parameters: 'auto ref T t' as 
a parameter says - make one or two functions with different 
signatures and same body based on how it is called. If called 
with rvalue use 'T t', if called with lvalue use 'ref T t'. From 
this thread: 
http://forum.dlang.org/thread/4F84D6DD.5090405@digitalmars.com?page=1 
it sounds like this will no longer be necessary since in the 
future a single signature of 'ref T t' will support both lvalue 
and rvalue. So, for now it is best to start with 'ref T' instead 
of 'auto ref T' unless there is really an interim need for 
support of passing literals/rvalues into the function. One 
downside to 'auto ref' is it has the power to combinatorially 
increase the number of instantiations of each function. The 
upside is it allows rvalues to be passed in until the ultimate 
solution is implemented. In generic code, if you don't mind 
requiring lvalues of users, don't bother with auto parameters at 
all and stick with 'ref'.


More information about the Digitalmars-d-learn mailing list