We could switch from ..
to ..<
for half open range and from ..=
to ...
for closed/inclusive range. IMO, Swift got this right.
This was bikeshedded to death; let's please not reopen it.
I understand the sentiment and I’m not going to push for any changes to stable syntax. The only reason I feel the need to mention it is because changes to stable syntax were previously unthinkable. Now they’re not. Please note my careful wording: “could.”
EDIT: Apologies if this was discussed after the epoch RFC was accepted and I didn’t see it.
I would add a checked indexing operator too : [?
.
pub trait TryIndex<Idx> where Idx: ?Sized, {
type Output: ?Sized;
type Error;
fn try_index(&self, index: Idx) -> Result<&Self::Output, Self::Error>;
}
variable[?10]
would be sugar to variable.try_index(10)?
.
The most obvious new operators for me would be:
- Euclidean modulo / division.
- These should probably be more commonly used than the
%
and/
operators, since they have much nicer mathematical properties. - Traits could be called
Mod
orModulo
andEucDiv
orFloorDiv
. - I suggest we could repurpose the
mod
keyword so you can writelet x = -3 mod 4; // x = 1
-
let x = -3 div 4; // x = -1
(potentially use backticks here?`div`
?)
- These should probably be more commonly used than the
- Wrapping operators.
- These currently are very verbose and quite needed for a lot of applications. To make the code neater you can wrap the types but that’s pretty awkward too. Sometimes I just use regular operations to make it look neater when the real operation I wanted was wrapping, causing potential bugs in debug mode
- Traits would be
WrappingAdd
etc. - Simplest is to combine a common sigil, say
%
with the existing operators, so you would havelet x = 255u8 +% 2; // x = 1
let x = 254u8 *% 4; // x = 248
- Conversion operator
- Something simpler to write than
TryFrom::try_from
for converting between types - Trait would be From / TryFrom
- Syntax bikeshed:
let x = 19u32 as? u8; // x = Ok(19)
let x = 256u32 as! u8; // error, no implementation of From
- Something simpler to write than
@kornel @varkor @djzin My initial vehicle for learning Rust (late last year) was to code variants of a strong cryptographic algorithm that is lightweight enough for retrofit embedded use. The lack of lexemes for
-
add
,sub
, andmul
for unsigned finite-ring arithmetic (e.g., u32) - as well as
rotate_left
androtate_right
- and the assign variants for these five operators
led me to delve into the compiler and work out the many changes that would be needed to add such lexemes and their associated trait language items.
The lexemes I considered for finite-ring arithmetic were +%
, -%
, *%
, +%=
, -%=
, and *%=
; those for rotate were <<<
, >>>
, <<<=
, and >>>=
. For Rust, I saw some advantages for using the trailing %
rather than Swift’s leading &
:
-
the trailing
%
expresses the order of operation: firstadd
/sub
/mul
, then take the remainder of the result (as an unsigned integer) modulo the representation size, then optionally assign that value to the first operand; -
lexeme augmentation with the
%
suffix makes less jarring the potential inclusion of Euclidian modulo and associated Euclidian divide operators (%%
and\%
, respectively). -
the
&
sigil used in Swift’s finite-ring operators is already used heavily in Rust, more than other languages, due to its use as theref
operator, so not using it for the finite-ringadd
/sub
/mul
operators reduces potential confusion
I personally feel that Swift’s &+
, &-
, and &*
operators emphasize the “bit-fiddling” aspects of the operations while suppressing awareness that the underlying arithmetic has moved from the infinite ring of all integers to the finite ring of all integers with a given representation. For the same reason, I looked at what it would take to limit the new lexemes to unsigned arithmetic, so that applying the existing wrapping operators to signed integers (which are operations in a very different finite ring) would always require more-verbose programming effort. (Unfortunately, I concluded that tracking known signedness of integer expressions would require extensive changes to the compiler. That conclusion also implied that new lints for distinguishing use of wrapping and rotation operators on signed values would also be difficult.)
FWIW, I eventually decided that cryptographic primitives should use the approach found in the octavo crate, declaring the needed finite-ring types as wN
(rather than using uN
), together with the mathematically-appropriate traditional +
, -
, and *
operators and the W
type-conversion operator for constants.
Since I'm unfamiliar with octavo, how (if at all) do its wN
types differ from type wN = Wrapping<uN>;
?
They don’t differ at all. The link in my post simply shows how that particular crate makes use of the wrapping trait more palatable. The key element for me is that use of
pub use core::num::Wrapping as W;
#[allow(non_camel_case_types)]
pub type w32 = W<u32>;
#[allow(non_camel_case_types)]
pub type w64 = W<u64>;
shifts the conceptualization of the crypto algorithms from the infinite ring of all integers, albeit constrained to u32
or u64
(subject to representation overflow or saturation), to the finite rings of 2^32 and 2^64 elements. It’s the clarity in mathematical conceptualization that I find attractive. Use of w32
, w64
and typed constants such as W(0x9b05688c2b3e6c1f)
let Rust’s type-checking detect accidental conflation of the rings. This benefit doesn’t accrue with u32
and u64
and use of lexemes such as *%
for the wrapping operators on those infinite-ring type values.
What i think would be a neat addition to the language are the monoid operators (+)
and (*)
.
This would use two traits: MonoidAdd
and MonoidMul
. Both traits define the operator function and an id function.
Why? Monoids are extremely useful. Most objects we deal with while programming can be expressed as monoids. This would give us two dedicated all purpose operators for library code, which would (imo) be a sweet spot between an explosion of domain specific operators and misuse of existing operators for other purposes.
Why two monoids? Several reasons. There are some semantic connotations with (+)
and (*)
, so libs can choose the most fitting one. For example list concatenation would use (+)
. Matrix multiplication would use (*)
.
Also, there are often more then one important operations that can be used in a monoid. And most importantly, this would allow the definition of a ring structure. I would imagine a library to augment this traits with traits for things like commutativity and additional mathematical structures like groups and rings and a bunch of general methods to operate on said structures.
Why the tokens (+)
and (*)
? An obvious alternative for the first one would be ++
. But **
is problematic because infix *
before prefix *
is already legal (for example 2**&2
). Also i quite like how (+)
resembles the mathematical notation of circled plus and multiplication.
I think this would get us a lot of bang for the buck in terms of generality. The cost would be the introduction of a fairly mathy concept into the language and one could argue that that is not very inclusive to people without formal computer science education. On the other hand, in other areas rust doesn’t shy away from theory when its useful and everyone who has studied one or two semesters of computer science, engineering or any other math-adjacent course has seen this before. And “use (+)
to concatenate lists and to merge sets” doesn’t require theoretical understanding.
Sorry for the wall of text. If there is any interest in that, i could open a tread.
Maybe this can be combined with @Uther’s proposal for checked arithmetic. Something like +!
, -!
. /!
, *!
.
Edit: I just saw the proposal of using %
as suffix - which makes totally sense after a bit of thinking - two new operators as%
/ as?
would would fit nicely into this scheme.
For what its worth, a way to write this that you will either love or hate is a[n..][..3]
(for the positive case).
My original idea was to propose operators with exclamation mark too, but it would be inconsistent with negation syntax.
Would value1+!value2
means (value1) + (!value1)
or would it means (value1) +! (value2)
uther, not an expert but i think it means (value1) + (!value1), but i can be wrong. will appreciate more experienced people to answer this. i am in search to buy hgh that’s why it is hard for me now.
This was a rhetorical question, since currently the +!
syntax does not exist. Currently, it obviously means (value1) + (!value2)
.
That’s why introducing the ̀+!` operator would be a problem. It would either break badly a lot of current code, or it would be unusable without always using parenthesis on the right side.
Something that composes and decomposes tuples would be nice…(not necessarily new
I would just like for Index
to be able to return a Proxy.
+
is usually commutative in mathematics (with one exception: ordinal arithmetic), so I don't think it would fit lists. The monoid operation of free monoids – the usual formalisation of lists in algebra – is usually written as juxtaposition, IIRC; '+' is preferred for abelian groups. As for sets, I think reusing bitwise operators would fit them better (|
for union, &
for intersection, ~
for complement): in a sense, bitwise operators already reinterpret a number as a set, after all.
I’m not sure how serious I am about this, but… how about infimum and supremum operators? For total orders, those are synonymous with minimum and maximum (i.e. for Ord
types, they would reduce to std::cmp::min
and std::cmp::max
), but potentially they could be used much more generally:
- A set type could define infimum as set intersection and supremum as set union;
- A (grid-aligned) rectangle type could define infimum as intersection of rectangles, and supremum as the smallest rectangle surrounding its operands;
- A list type could define infimum as the longest common prefix of its operands;
- A ‘division lattice’ wrapper for integer types could define infimum as GCD and supremum as LCM;
and so on. I do realise bitwise operators can be semantically similar at times (as I noticed in my previous post), but I think having dedicated operators known (or at least expected) to agree with the order relation on the type would be much more useful.
Also, given that attempting to re-use ASCII punctuation for new tokens can give rise to so many incompatibilities and syntactic ambiguities… how do you feel about using non-ASCII characters for new operators?
With both in mind: traits Meet
for ∧
(infimum) and Join
for ∨
(supremum). (Names come from algebra; Inf
and Sup
could also work.) Or you could have them spelled the way they almost were in K&R C: /\
and \/
.
You alrady know my opinion about this, but this is a different thread and I also have something new to add. I propose that we don’t add more operators to Rust. The language’s direction would be better off without more magic punctuation.
Early Rust used to have lots of special symbols, way more than it was reasonable. It has been fixed before the 1.0 release by removing or replacing most of them (e.g. ~
and Box
), and only leaving those that were immediately recognizable.
This was a community decision, people preferred actual readability and easy searchability over “terseness above everything else”.
I’m with @H2CO3’s sentiment, that “magic punctuation” can be a real burden for code clarity
I like
- The checked arithmetic operators (
+?
,*?
, etc.) proposed by @Uther because they’re essentially a combination of two existing concepts: The usual arithmetic operators and the try operator (?
). But ONLY if they are 100% equivalent toa.checked_add(b)?
etc. (This means that theTry
trait implemented onOption
would need to be stabilized first) In any case, reserving these as operators can’t hurt. - The
mod
operator. It’d be exactly the same notation as in mathematics andmod
is already a keyword. It doesn’t fall into the “magical punctuation” category because every programmer who sees it immediately knows what it means.
I don’t like so much
- The monoid operators
(+)
,(*)
, etc. IMO they are very niche. Libraries could just define the same ops with the existing+
,*
etc. and it’d be fine. I think that a special notation with only the purpose to emphasize mathematical connotations is not enough of a reason to introduce new operators into the language.
Here’s an idea
Maybe Rust could get custom binary operators which are just normal methods with an annotation that enables an additional calling style for them. It would avoid the introduction of several weird special character operators like !==~
to do whatever.
impl Vector3 {
#[binary_operator]
fn dot (&self, v2: Vector3) -> Vector3 { ... }
}
let value = (vec1 dot vec2) dot vec3; // New
let value = vec1.dot(vec2).dot(vec3); // Current way