Can sanitizers find the two bugs I wrote in C++?
The problem of hidden types surfaces again.<p>Obviously passing null to a function that does not expect null is a well known problem, tackled by many programming languages but not by C and C++. This is a purely typing issue, because null is a different thing than a valid memory address.<p>The other issue about vector.pop_back() is also a hidden type issue...an empty vector is not the same as a non-empty vector, and therefore pop_back() applies only to non-empty vector.<p>What would a possible solution be in the context of C++?<p>Well, each function should only be applicable when the parameters are in the states the function expects. So parameters (including this&#x2F;self) shall have states.<p>For example:<p><pre><code>    void pop_back() this : not_empty { 
        ...
    }
</code></pre>
But this is not enough. The language should allow the user to specify change of states at certain statements&#x2F;expressions. For example:<p><pre><code>    void clear() {
        ...
        this := empty;
    }
</code></pre>
And then the compiler can check the AST if the used vector has the &#x27;empty&#x27; state on pop back or not. Doing the following would be illegal:<p><pre><code>    vector&lt;int&gt; data;
    data.clear();
    data.pop_back();
</code></pre>
And the following would be illegal because one execution path would lead to a state violation:<p><pre><code>    vector&lt;int&gt; data;
    if (some_external_condition) {
        data.clear();
    }
    data.pop_back();
</code></pre>
And the following would be illegal too, because the result of clear_random_vector could be possible in empty state.<p><pre><code>    vector&lt;int&gt;&amp; clear_random_vector(vector&lt;int&gt;&amp; a, vector&lt;int&gt;&amp; b) {
        if (rand() &lt; RAND_MAX &#x2F; 2) { &#x2F;&#x2F;c++ has better random number interface
            a.clear();
            return a;
        }
        b.clear();
        return b;
    }
</code></pre>
int main() {
    vector&lt;int&gt; a{1, 2, 3}, b{4, 5, 6};
    vector&lt;int&gt;&amp;v = clear_random_vector(a, b);
    v.pop_back(); 
}<p>And in case the function clear_random_vector is in another translation unit, then:<p>a) if headers are used, the programmer shall provide metadata about the state of the result in the header. Example:<p><pre><code>    vector&lt;int&gt;&amp; clear_random_vector(vector&lt;int&gt;&amp; a, vector&lt;int&gt;&amp; b) := empty;
</code></pre>
b) if modules are used, the compiler itself should output the metadata.<p>Actually, this would also solve the null pointer problem, because a pointer would have the states empty, not_empty by default, and then functions could be like this:<p><pre><code>    memcpy(void* dst : not_empty, const void* src : not_empty, size_t count);
</code></pre>
And then this would be caught at compile time:<p><pre><code>    std::vector&lt;char&gt; dst, src;
    memcpy(dst.data(), src.data(), 10);
</code></pre>
States could also solve the problem of array indexing. For example, an index can have two states, &#x27;in_range&#x27; and &#x27;out_of_range&#x27;. Using an integer as an array index would implicitly add the two states in the index variable; comparing the integer with the array range would automatically add &#x27;in_range&#x27; or &#x27;out_of_range&#x27; to the integer.<p>Example of valid usage:<p><pre><code>    vector&lt;int&gt; data{1, 2, 3};
    int i = 0; &#x2F;&#x2F;our index
    &#x2F;&#x2F;&#x27;i&#x27; gets automatically &#x27;in_range&#x27; state.
    if (i &lt; data.size()) { 
        std::cout &lt;&lt; data[i] &lt;&lt; std::endl;
    }
</code></pre>
The following would be invalid, because straightforward array indexing would requre a state named &#x27;in-range&#x27;:<p><pre><code>    vector&lt;int&gt; data{1, 2, 3};
    int i = 0; &#x2F;&#x2F;our index
    std::cout &lt;&lt; data[i] &lt;&lt; std::endl;
</code></pre>
How would that work at language level? well, here is an idea: a) any operation can affect the compile-time state of objects, b) states themselves would be templates, bound to certain ids at compile time.<p>Here is a possible implementation:<p><pre><code>    template &lt;class T&gt; class vector {
        &#x2F;&#x2F;returns a &#x27;size_t&#x27; variable tagged with state &#x27;size_of_this&#x27; and parameter &#x27;this&#x27;; in this case, this does not refer to the runtime address of the object, but to the identifier this object is bound to.
        size_t size() const : size_of_this&lt;this&gt; {
            ...
        }
    }

    &#x2F;&#x2F;index is bound to state bound_index&lt;T&gt;, where T refers to the variable of the vector at compile time.
    template &lt;T&gt; operator &lt; (int&amp; index, size_t size: size_of_this&lt;T&gt;) {
       index := size_of_this&lt;T&gt;;
    }

    template &lt;class T&gt; class vector {
        &#x2F;&#x2F;the vector operator [i] requires size_of_this&lt;T&gt;
        const T&amp; operator [](int index : size_of_this&lt;this&gt;) const {
            ...
        }

        &#x2F;&#x2F;adding an element to the vector breaks size_of_this.
        void push_back(const T&amp; elem) {
            this := !size_of_this&lt;this&gt;;
        }
    }
</code></pre>
So, each time push_back is called on a vector, the guarantee about indexes breaks, and the index that previously holds the guarrantee can no longer be used for indexing.<p>The above also requires metadata at header&#x2F;module level.<p>By all means, the above is not feature complete, it is some idea on how it might work to have compile time guarantees...and if it looks suspiciously like Rust&#x27;s lifetimes, I am sorry, I don&#x27;t know Rust, any similarity is completely random.<p>And perhaps this mechanism may be used for lifetimes as well, I don&#x27;t know ;-).