Is it good to allow some function generate implict mut variable?

And going out of scope will call drop by definition. Therefore, the only non-mut values are the ones which are wrapped at some point in ManuallyDrop and not dropped manually.

1 Like

Thanks for your correction.

maybe we should not consider implict drop directly.

and, while calling functions, we could wrote function's signature as:

fn consume(mut v:Vec<i32>)->...

to tell the caller v is mutable here.

Note that this is already valid syntax, but it means something different from what you want it to mean. It means the variable v is mutable, but that's local information that doesn't affect callers.

I know it is legal.

I wrote fn ..(mut ..) here just for discussing when to supress unmut_must_mut warnings.

If we wrote let v=Vec::with_capacity(20), that vec should be mut, thus an warning might be generated.

but if we put v into fn(mut v:Vec<i32>), the signature of function suggests v is re-bind as mut, thus no need to generate such warning

To me the idea seems more appropriate as a #[must_mut] annotation on a function rather than as the mut keyword on the return type.

However, it's not clear to me what this annotation would really mean because it sounds like you want to require mutation not necessarily the return value directly, but on anything derived from that value, which gets into fuzzy territory.

For instance, suppose String::with_capacity has that annotation. Is this OK?

fn foo() -> String {
    let a = String::with_capacity(100);
    a
}

How about this?

fn foo() -> String {
    let a = String::with_capacity(100);
    let b = a + "abc";
    b
}
1 Like
fn foo() -> String {
    let a = String::with_capacity(100);
    a
}

we suppose that a should be mut, thus

#[must_mut] // your notation
fn foo() -> mut String { // my notation
    let a = String::with_capacity(100);
    a
}

is enough.

as for

fn foo() -> String {
    let a = String::with_capacity(100);
    let b = a + "abc";
    b
}

since we have

#[cfg(not(no_global_oom_handling))]
#[stable(feature = "rust1", since = "1.0.0")]
impl Add<&str> for String {
    type Output = String;

    #[inline]
    fn add(mut self, other: &str) -> String {
        self.push_str(other);
        self
    }
}

that's fine.

Actually, I prefer mut Return than #[must_mut]fn..., since we almost do the same things, mark either parameter and return value as mut

How about this:

let mut a = std::convert::identity(String::with_capacity(100));

This would generate a warning, right?

In the case that we do not allow the mut symbol infect std::convert::identity, this should generate a warning.

since we may allocate huge amount of vectors in a function at once:

let mut x:Vec<Vec<i32>>=(0..10).map(|i|Vec::with_capacity(i)).collect();

if we have a mut binding, it might be better to allow the mut keyword supress all warnings.

Do you have another use case besides with_capacity?

It doesn't seem that terrible to create a vector with_capacity and never put any values in. After all, if it's a bug, you are even more likely to do this with Vec::new() and then this warning won't protect you. And with all the false positives it seems like a warning with dubious value.

use std::fs::File;
use std::io::Write;
fn some_complex_function()->&'static str{
    &"bar"
}
fn main() -> std::io::Result<()> {
    let mut f = File::create("foo.txt")?;
    let mut b = File::create("bar.txt")?;
    b.set_len(13)?;
    let z = File::create("baz.txt")?;
    z.set_len(10)?;
    match some_complex_function() {
        "foo" => f.write_all(b"Hello, world!"),
        "bar" => b.write_all(b"Hello, world!"),
        _ => Ok(())// suppose we forgot `baz`
    }
}

In this case, we forgot writing baz, but no warning generates.

Things should be different if File::create("bar.txt")? is marked as mut.

The problem with this notation is that it suggests that if the mut keyword wasn't there, you would not be allowed to assign the output to a mutable variable, which is not what you mean. It's the converse of what the mut keyword means elsewhere (where it's a permission). That's why I suggested #[must_mut] instead.

It's an interesting idea but the problem is that you'll get false positives because in Rust there is no concept of the identity of an object being preserved when it's copied or moved, nor does it really make sense to introduce such a concept.

fn concat_strings(a: String, b: &str) -> String {
    a + &b
}

fn main() {
    // false positive
    let s = String::with_capacity(100);
    let s = concat_strings(s, "hello ");
    let s = concat_strings(s, "world");
    println!("{s}");
}

How might you articulate this given that a Rust type encodes permissions to the value + “type of value”? Is permission not something “intrinsic” to the Rust type system?

What "types" are or what constitutes a "type system" is ambiguous. In the most restrictive sense, one might say that the type of a value is simply a tag that distinguishes a given value (or bit pattern, etc.) from identical but differently-typed values.

On the other hand, this definition is not useful but for the most rudimentary static analyses. It's not in itself sufficient for ownership checking, for example. What we usually mean by "type checking" in modern compilers is the checking of all semantic guarantees that the language offers; in this sense, for example, checking of mutability and sharing, borrow checking, drop checking, and thread safety checking are all part of "type checking" in Rust.

What I'm trying to say with "mutability is not a property of types or values" is that there is no such thing as an (im)mutable value or an (im)mutable type. Given any type T, one can freely declare a binding let x: T or let mut x: T, and given any value v, one can bind it to and move it from an immutable or a mutable binding. So in this sense, based on the definition of a type, or the value of some expression, you don't know whether it was, or is going to be, mutated. That information is only known when you look at the relevant bindings; the only exception being references where mutability is part of the type, but it's still not about &T versus mut &T; it's about &T versus &mut T, and for a good reason (typically, indirection complicates reasoning).

I think in theory one could think of mutability vs immutability as part of the type, but it's not very useful to do so. One would then have to talk about implicit coercions that can flip this bit on each move.

It's just simpler to think of this as not being part of the type, but rather as a tag on each variable.

3 Likes

C++ has const as part of the type which creates some additional possibilities (and some additional complications), for instance you can have a type std::pair<const int, int>. There is no such thing in Rust because mut is not part of the type -- (i32, mut i32) is not a type.

1 Like

I take your point that what we are talking about is not inherently part of the value type (without letting value mean ref value for purposes here). Type equality in Rust for the refs we are talking about seems to depend on the “rights”. Case in point, for any given trait, I might need to specify an implementation for each of the &T, &mut T etc. how do you think (model) that?

I’m laboring on this point because I do see value in recognizing that we are dealing with a “product type”, (T, rights) ~ U. It’s useful for all the reasons that a borrow is different from an exclusive borrow. The Trait lookup treats them differently… a system that is indexed by type. Right?

To be honest, I understand almost nothing of this.

You do that by writing separate impls, and the type system models that by distinguishing between &T and &mut T as distinct types.

I'm not sure what you are getting at here. Trait lookups don't differentiate between "T when its instance is bound to a mut binding" and "T when its instance is bound to an immutable binding", since that's just not a piece of information that is available to the trait system. &T and &mut T are different types on their own right, but there is no difference between

let x = Foo;
Trait::bar(x);

and

let mut x = Foo;
Trait::bar(x);

as far as the trait solver is concerned.

Precisely. For me this suggests we are dealing with different types and thus submit that the access rights to a value is captured in the type (an inherent property).

My statement does not hold for the difference between owned values, whether mut or not. I agree with distinction per your statement that calling the trait method on mut x vs x are one the same when it comes time to using types to map to trait implementations.

My apologies if I misunderstood where parts of the thread were suggesting otherwise. I was likely prone to this misunderstanding because I can't recall where it was critical for the Rust compiler to know the difference between owned, and mut owned. I "bucketed" it as a useful signal for conveying intent and facilitating optimizations. After all, with ownership come the rights to mutate it (essentially true because there is nothing preventing a recasting of the quality of the ownership... without changing what was promised to the borrow-checker). I'll have to read through the threads to learn how that thinking needs to be adjusted.

Do you see how the unsafe and mut flags might be a consistent application of a Rust philosophy that encourages encapsulation and being explicit when it's time to "switch mindset"?

If the above is useful, for me the ability to shadow the ref name to explicitly and precisely convey a toggle from a "mut" frame of mind, to one that is read-only is something I find useful when writing larger blocks of code. It also avoids having to come-up with a new name that might introduce undo confusion in my intent [1].


  1. that said, sometimes I postfix _mutable... ↩︎

That looks like it makes sense for the 'with_capacity' method. But it looks like a special case, in general I think this would be terrible. Consider:

let something = Something::something();

Now as a reader of that code I can't tell if something´ is intended to be mutable or not. To find out I have to know whether Something::something()` returns a mutable thing or not. In general I could never tell if anything was mutable or not unless I knew the mutability of the thing returned. This does not sound good to me.

All that trouble just to save typing 'mut' here and there. Does not seem like a good deal to me.