Why `*const T` and not `*T`?

Currently we have:

reference raw pointer
&T *const T
&mut T *mut T

And I wonder why whe require the const when using a raw pointer and not when using a reference. I understand that this is similar to the way C does it but I think concistency within the language is more important that similarity to other languages. When I first saw a *const T I thought it was a pointer to a constant value of T and I wondered why we would need a different pointer type for that. I think we hide the similarities to &T a bit which makes it less intuitive.

It is weird that a raw pointer uses the same symbol as the dereference operator * but that is caried over from C and to late to change. However using *T in the type does not introduce any parsing problems as far as I can see as you can only use the operator in expressions while *T is only used when giving a type.

Because in C T* is a mutable pointer, so it is confusing when *T in Rust means a const pointer.

See https://github.com/rust-lang/rfcs/pull/68 for detail.

5 Likes

But in C every variable is mutable by default while in rust every varaible is immutable by default so I think that would be very much expected. In C a int& is also mutable by default while in rust a &i32 is not.

(Tangentially related: this twitter thread, wherein I floated the possibility of deprecating all of *const T, *mut T, and NonNull<T>, and replacing them with a new *T type with the same semantics as NonNull<T> – mutable, covariant, and non-nullable – but was dissuaded by @Manishearth from pursuing the idea further. Apparently, this might be good for within-Rust uses, but not as much for FFI. It wouldn’t have the “mutability confusion” issue @kennytm mentions though, because it would be mutable, just like T* in C.)

1 Like

Raw pointers are mostly used for FFI with C. This means that if you’re translating an interface manually, you’re far more likely to make a mistake. It also means that if you get it wrong, you’re not going to get any sort of error: your code will just be silently wrong.

The comparison to variables and references isn’t valid: if you get mutability in those contexts wrong, you get a compile error or a lint warning. FFI has no such safeguards.

Edit: Also, IIRC, const is technically not the same thing as "not mut" on a reference: in C, it’s valid to cast const away, which is not true of immutability in Rust. So there’s also that.

That's C++, reference types don't exist in C.

3 Likes

It’s a decision that was made to try to match C syntax for FFI. But, in the end, the syntax feel bad for both languages.

  • *const does not even match very well the C syntax since the star come after the type in C. But it’s completely awkward from the Rust point of view since immutable types are usually not prefixed and the const keyword has a completely different meaning.

  • *mut match pretty well the Rust syntax, but absolutely not the C syntax.

I think Rust should have stayed coherent with itself. If C syntax was absolutely needed for FFI, a macro may do the translation. Somthing like c!( int * ) expand to : *mut c_int.

3 Likes

While normally I also am in favor of consistency, personally I think the Rust developers made the right tradeoff ('cause that’s what it is):

  • While the current choice is not all that consistent with the rest of the language on the surface, it can easily be argued that any unsafe code is not like the rest of Rust in that the semantics differs significantly between raw pointers on the one hand and borrows and ownership on the other.
  • When doing C FFI, the const acts as an extra “watch out here!” by explicitly marking non-writable raw pointers as such. This is useful, especially since C errs to the side of mutability.
  • The real goal of consistency is to make similar-but-different features easier to use, by leveraging what a programmer already knows. But as I stated above, the semantics between the raw ptr camp vs borrows/ownership seem to differ as least as much as they are related, and the differences are tiny but impactful.

None of this can make the argument that having the const is better than omitting it. But there’s one factor that is: it is already in play, and people are already using it. Quite often during language discussions it appears to me as if the cost of changing what people know is essentially “low enough to be worth it”. But I see 2 issues with that:

  1. There will be relearning fatigue among Rustaceans at some point, and the current discourse in the various threads this forum does not seem (to me at least) very cognizant of the budget-like nature of this aspect of humanity. Therefore I think there should be a relatively strong bias against changing things that already work in Rust today, in favor of extending them to do new things.
  2. If the way to do something is changed, this actively hampers learnability: a new Rustacean would have to wonder which to use: *T or *const T, and the only things that would enlighten them are either some (possibly outdated) post on StackOverflow or reading the discussions and/or the RFC. This is ultimately somewhat workable but wastes prodigious amounts of new Rustaceans’ time. Incidentally, this is another thing that Rust feature proposals could (should?) be more cognizant about.
  3. If changes like these are implemented, suddenly we have a more complex language to deal with: some projects will use *T, others *const T, and Rust programmers will be required to know both forms and deal with each. Worse yet, the rust compiler won’t ever be able to live down this complexity as neither regular releases nor Epochs (or whatever they’re called nowadays) allow for breaking changes e.g. removal of deprecated APIs.
    This is a simple example, but each time new syntax is suggested for things that are already possible this point will be a constant. And worse, the effects of such implemented proposals accumulate and grow exponentially.
4 Likes

I think that’s just because the nature of this forum is to discuss possible language changes, many of which come from people unfamiliar with the past several years of discussion and prior art on various issues. We already do a very good job imo of avoiding change for change’s sake at the RFC stage and earlier; most undermotivated proposals simply never go anywhere.

There are many threads on this forum to which my only response would be “I don’t find the motivation compelling enough to justify this change”, and I generally don’t post that since it seems... not quite rude, but it’s one of those responses that “shuts down the discussion” in a frustrating way as there’s no meaningful reply to it. I assume I’m not the only one thinking like that, and that’s why the typical “crazy idea” thread seems to consist solely of people that all think it’s a good idea (or maybe just enjoy pondering it).

2 Likes

One the one hand I see your point. On the other, doesn’t the very act of self-censorship shift bias in each those threads?

The thing that gets me is that the threads I’m talking about all share a property: rather than adding something truly new and innovative, they tend to be proposals for surface syntax changes, which are highly visible but have low or perhaps even negative value due to their cost in terms of the social component of the Rust ecosystem.

In an ideal world this forum itself would inform a would-be poster about such things I suppose (allowing them to focus their efforts on higher-yield ideas), rather than racking up topic after topic in which the same things need to be explained over and over again. It’s essentially the whole “earlier feedback is better” concept from REPLs applied to the social realm. I don’t think that would be rude; quite the opposite, since it saves everyone that would be involved time. The main issue I see with that is that automatically telling a user their idea is low-yield before they even finish typing, and without a reasonable suggestion for a solution (which I don’t consider automatable without AGI), tends to demotivate that user.

So where does that leave us?

My point is that I believe it was a bad choice to try to look vaguely like C at the cost of language coherence.

But I did not say that we need to switch back to the old syntax, it’s too late for that. I agree that even if the edition system make syntax changes possible, we should avoid them as much as possible.

1 Like

For intra-edition changes, I agree wholeheartedly. For inter-edition changes, where we can provide rustfix support, a marketing push, and a conceptual break between Rust idioms, I would argue that these changes should be fully considered. The purpose of the edition is to signal conceptual changes, and their longer time-horizon means that changes can be announced long before the edition lands. Assuming versions seek to limit change fatigue, editions may leverage hype to overcome it.

1 Like

In principle, absolutely. But this approach is and should be a last resort, and nothing in this thread comes close to the level of motivation required for that.

Or in other words, my thoughts on this are basically the same as this thread I posted on a few months ago:

3 Likes

Rustfix only fixes parts of the code migration issue though. Things like Rustaceans learning the new concepts, and them learning to cope with mixed code (i.e. they use some old, already-supplanted features, and some new ones) will ultimately also be required as extant Rust code bases grow more complex. So rustfix is quite nice to have, but doesn’t do anything to mitigate the social costs. And it’s these social costs that I’ve noticed myself growing somewhat more concerned with as time moves on and as the community grows.

3 Likes

I doubt that, taken alone, most of the "language tweak"-grade changes would meet the level of motivation required. However, as the language evolves, many such small inconsistencies will accumulate. It's not inconceivable that a Rust edition 6 to 10 years from now might resolve to fix a number of these inconsistencies, in the interest of making the language more uniform, because the collective burden they impose is greater than any of them individually.

That argument, of course, relies upon an uncertain future. I offer it only because it mitigates @jjpe's issue number 3 here.

@jjpe

I believe the social costs justify your assertion that these changes should not be made arbitrarily. My assertion is that releases are too arbitrary, but editions are not. Editions represent a break with old habits; they can be relied upon to ameliorate the social costs, when used responsibly.

I agree that minor changes like this likely have low or negative value individually. They might make an interesting use-case for Niko's RFC-repo idea, however, containing a swarm of micro-RFCs that eliminate "death by a thousand papercuts".

2 Likes

This sounds like it's assuming the syntax *const T is an inconsistency that everyone agrees is suboptimal, and if backwards compatibility was a non-issue we'd happily "fix" it today. Like most minor surface syntax proposals of this sort, there's nowhere close to a consensus on whether a different syntax would be better. Plenty of people (including myself) think the current syntax is not merely good enough but probably optimal.

For actual "small inconsistencies" where more or less everyone agrees there is a problem and agrees on the desired fix and the desired fix is a breaking change, yes of course we should use editions. We're probably already doing that, depending on what you consider "small".

1 Like

To be clear: My argument is responding to @jjpe's argument about social fatigue, no more and no less. My argument, insofar as it relates directly to *const T, is that it may fall into a category of "language warts" that are best dealt with collectively several editions from now. The purpose of the "swarm of papercuts" RFC repo would be to collect all such warts in one place, so that we can determine which, if any, are worth solving when that moment presents itself.

1 Like

Are there any known examples of minor, uncontroversial language “warts” that would go in such a repo?

All the minor language changes I’m personally aware of are either highly controversial (or at least, high relative to the small potential benefits, like the : vs = in structs thing or this *const T thing) or don’t even need editions and can just go through an ordinary warning cycle and then get deprecated. https://github.com/rust-lang/rust/issues/30459 being a good example. We could use an edition to just wipe out all deprecated items, but since the compiler has to continue supporting all those items for older editions anyway, I assume there’s no point in doing that.

I took a look at all the issues labeled rust-2-breakage-wishlist to see if there were any good examples in there, but there weren’t.

Most of them are simply breaking changes to std, which the editions RFC specifically states cannot be done with editions (in fact, that part of the editions RFC uses https://github.com/rust-lang/rust/issues/35943 as its example). So there’s not much point gathering more examples like that.

The remainder aren’t great examples either for various reasons. One of them needs non-trivial design work so it’d probably go through a normal RFC if anyone did that work. One is simply incorrect. One is irrelevant now that ? works on Options. One would be a very controversial bikeshed. And finally there’s this minor issue involving CLI flags, which isn’t really what we’re looking for either.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.