Skip to content
This repository has been archived by the owner on Aug 28, 2023. It is now read-only.

Inconsistent position of 'const' between immutable targets of pointers and slices #16

Open
dolmen opened this issue Oct 4, 2018 · 7 comments
Assignees

Comments

@dolmen
Copy link

dolmen commented Oct 4, 2018

In Go, slices are just iterable pointers with safe bounds (safe thanks to runtime enforcement).
The type declaraction is consistent: the derefencing is on the left of the element type.

Here is some Go code that use mutability to indirectly modify the same memory location first through a pointer, then through a slice:

package main

import (
	"fmt"
)

func main() {
	ints := [1]int{42}

	ptr := &ints[0]
	fmt.Println(*ptr)
	*ptr++
	fmt.Println(*ptr)

	slc := ints[:1]
	fmt.Println(slc[0])
	slc[0]++
	fmt.Println(slc[0])

	fmt.Println(ints[0])
}

According to this proposal, to forbid modification through *ptr or slc[0] (*ptr++ and slc[0]++ should be rejected at compile time), we have to declare like this:

ptr := * const int(&ints[0])
slc := const []int(ints[:1])

References:

I argue that the position of the const keyword in the type types expression are inconsistent: the const is on the right of * but on the left of [].

This is a follow-up of my tweets at https://twitter.com/omengue/status/1047743716461629440

@romshark romshark self-assigned this Oct 4, 2018
@dolmen
Copy link
Author

dolmen commented Oct 4, 2018

It looks like the inconsistency has been noticed as the entries of the FAQ seem to use instead a syntax for immutability of slice elements that is consistent with immutability of pointer dereferencing.
So at least section 1 and 2 should be fixed to match examples in section 4.

@romshark
Copy link
Owner

romshark commented Oct 5, 2018

According to 2.8 Address Operators and 2.7 Slice Aliasing the results will be the following:

var ints const [] const int = []{1, 2, 3}

ptr := &ints[0] // * const int
i := *ptr       // const int
*ptr++          // Compile-time error

slc := ints[:1] // const [] const int
i = slc[0]      // const int
slc[0]++        // Compile-time error

@romshark
Copy link
Owner

romshark commented Oct 7, 2018

Please consider closing this issue if you consider it resolved

@dolmen
Copy link
Author

dolmen commented Oct 10, 2018

My point is not how to change the type of ints. That's cheating.

In 1.1.4, two const are needed for full immutability:

var each2 = const [] const byte{'e', 'a', 'c', 'h'}

In 1.2.1 this question is quite misleading:

Will it mutate any T referenced by any item of v?

No, it can't. The Ts referenced by any item of v are immutable
Here is an expression that will modify the array pointed by the v slice:

v[0] = new(const T)

The type of v should be changed into [] const * const T to really ensure safety.

In 2.3, ReadObj is forbidden from modifying *obj and obj.MutableField, but nothing stops it from modifying obj.MutableField.MutableField because obj.MotableField is of type *Object. This is quite misleading about the immutability power of this proposal. It has the same flaws as const in C/C++.

@romshark
Copy link
Owner

romshark commented Oct 11, 2018

@dolmen

My point is not how to change the type of ints. That's cheating.

In 1.1.4, two const are needed for full immutability:

var each2 = const [] const byte{'e', 'a', 'c', 'h'}

In 1.2.1 this question is quite misleading:

Will it mutate any T referenced by any item of v?

No, it can't. The Ts referenced by any item of v are immutable
Here is an expression that will modify the array pointed by the v slice:

v[0] = new(const T)

The type of v should be changed into [] const * const T to really ensure safety.

That's all correct. In fact, most of the examples including slices and maps in the document are wrong because I, coming from a C++ background, initially falsely assumed that [] returns a copy of the item in the slice/map where &[] is just a combination of the index and reference operators taking the address of the copy returned from [] located on the stack and not the address of the actual slice/map item. I wasn't aware of the fact that in Go the index operator [] is no regular function (like a method implementing a slice "interface" or "trait") returning either a copy or a pointer but rather a built-in language construct (https://goplay.space/#PYb1K0nmmTL).

With a const qualifier it will really become a nightmare of const [] const * const T and that's why in the second revision I'll introduce mutability qualification propagation. Eventually the example will be:

var each2 = []byte{'e', 'a', 'c', 'h'}

If immutability will be default behavior, or if not then:

var each2 = immut []byte{'e', 'a', 'c', 'h'}

In 2.3, ReadObj is forbidden from modifying *obj and obj.MutableField, but nothing stops it from modifying obj.MutableField.MutableField because obj.MotableField is of type *Object. This is quite misleading about the immutability power of this proposal. It has the same flaws as const in C/C++.

Wrong. Even though obj.MutableField is of a mutable type, it's part of obj which is an immutable type const Object and is thus contextually immutable. Subsequently, obj.MutableField.MutableField is immutable too, because it's part of a (contextually) immutable type, which is part of an immutable type.

Having immutable composite types that don't protect their fields would make the entire concept useless similar to JavaScript const references because it wouldn't protect us from mutable shared state.

I think I should describe contextual immutability better (or did I miss that point entirely?!).

@romshark
Copy link
Owner

romshark commented Oct 11, 2018

@merhalak
In Go - types are read and written from left to right in a (western) human-friendly way.

// some absurdly complex type:
**[]map[int]*T

This is a pointer to a pointer to a slice of maps of integers to pointers to T. It's much easier to read than any C/C++ equivalent.

Mutability qualifiers should be no exception to this rule:

// an even more absurd one:
mut * immut * mut [] immut map[mut int] mut * immut T

This is a mutable pointer to an immutable pointer to a mutable slice of immutable maps of mutable integers to mutable pointers to immutable T.

A mutability qualifier always affects the type(s) on the right and propagates until it's canceled out (MQP).

Remember, those were just absurdly complicated examples, nobody will ever write things like this (people usually get hanged for doing so). You'll most likely see no more than:

mut [] immut *T

Which is a mutable slice of immutable pointers to immutable T (because immut propagates due to MQP).

@aploskov
Copy link

@romshark thanks for your explanation. Sorry for off topic.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants