Have you seen the trait ? It defintly looks like you can specify an arbitry Output
Your macro is cool but you have to write everywhere you need it which doesnt feel good at all, compare that with magic ! ! everywhere
Have you seen the trait ? It defintly looks like you can specify an arbitry Output
Your macro is cool but you have to write everywhere you need it which doesnt feel good at all, compare that with magic ! ! everywhere
Actually neg is the unary - so that would look like - - everywhere instead,
I guess std::ops::not looks better ! ! !
the problem is that, you couldn't both impl Not<Output=u32> for T
and impl Neg<Output=i32> for T
for the same T:
error[E0107]: this trait takes 0 generic arguments but 1 generic argument was supplied
--> src/main.rs:2:16
|
2 | impl std::ops::Not<i32> for T{
| ^^^----- help: remove these generics
| |
| expected 0 generic arguments
with the generics removed:
|
2 | impl std::ops::Not for T{
| ------------------------ first implementation here
...
8 | impl std::ops::Not for T{
| ^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `T`
you could make -x
or !x
have its own type diifferent from x
, but that type is unique, you could not re-define them
I once tried that in a unsuccessful github PR for a toy crate. That is useful when convert a BuildConfig
to an exact Build
since the convert should be unique.
But for .into()
, the result should not be unique.
I believe that, if I learn proc_marco in future, I may have the ability to implement a new marco that allow the following code works:
cai!{
fn main(){
let a:i32:=1u8;
let mut b=1i32;
b~a;
b:=a;
}
}
Could you share an example in playground? Errors further in the post does not correspond with this claim.
I am mainly talk about the idea changing x.into()
into !x
: that is not possible since .into()
could have more return type than std::ops::Not
struct T(i32);
impl std::ops::Not for T{
type Output=i32;
fn not(self)->Self::Output{
self.0
}
}
impl std::ops::Not for T{
type Output=u32;
fn not(self)->Self::Output{
self.0 as u32
}
}
fn main(){
println!("{}",!T(1));
}
// this trait takes 0 generic arguments but 1 generic argument was supplied
struct T(i32);
impl std::ops::Not<i32> for T{
type Output=i32;
fn not(self)->Self::Output{
self.0
}
}
impl std::ops::Not<u32> for T{
type Output=u32;
fn not(self)->Self::Output{
self.0 as u32
}
}
fn main(){
println!("{}",!T(1));
}
Late to the party and going off on a tangent ...
This is the reason for the Java/C++ dogma of Thou shalt make all data members private: following this rule prevents clients from being able to write let area = shape.area
in the first place.
In these languages, you can hide a field access behind a function-like interface, while you cannot hide function calls behind field-access-like syntax, so you make everything look like a function call. The price is having to write countless trivial getters and setters for most of your data. The reward is that you can change your mind about implementation details, without breaking client code.
Python gets around the problem with properties which make field-access syntax invoke functions: This allows you to hide function calls behind field-access syntax, as well as hiding field access behind function calls, so it doesn't matter whether you start off representing area
as a field or as a function: you can always keep up the pretence that it's still there, even after you've changed your implementation. Thus public data are perfectly acceptable in the interface of your classes in Python, which really freaks out those who grew up in Java or C++. I think that C# has something similar.
Bertrand Meyer considered this whole business to be crucially important to good software design, so he called it the Uniform Access Principle:
All services offered by a module should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation.
and made it one of the cornerstones of Eiffel.
I haven't written enough Rust to come up with an opinion about how to address this in Rust, but whenever I write a type with a pub
field, I do feel slightly uncomfortable, for exactly the reasons you mention.
Ah yes, Eiffel. Thought I'd seen Uniform Access somewhere. Had his book on Eiffel back in the day. Did anyone ever use Eiffel? I have never seen it in the wild.
I guess I should have spotted it in Python and/or C# but I have been trying to avoid those as much as possible.
Of course the next thing is that array access should look the same as a function call with a single usize
parameter.
Common (and other) Lisps do that: aref. But then Lisps do everything with the same single syntax, which is their fundamental strength.
I recall array access and function calls having the same syntax elsewhere ... MATLAB? Surely somewhere else too.
Anders Hejlsberg added properties to Delphi years before he joined Microsoft and developed C#.
Fortran is a tiny bit older, I would think. Although I'm not sure if it had functions back in 1957, or they were added later. This development happened many years before I was born, you see.
It would be interesting to know if Lisp did that before Fortran or after but it would have only purely theoretical interest: this happened before languages theory was developed, compilers were very ad-hoc and nobody was thinking twice about the fact that two very different constructs have the same syntax.
Note that Rust has several features that (almost necessarily) depend on field access instead of methods:
I don't mean to say that one should always prefer these over the Uniform Access Principle, but that it's important to consider how the type is going to be used and if these are desirable. Sometimes a type is so specifically defined that switching it to have computed results would not even make sense.
I can see the value in it, but there is also a cost. By obscuring the cost of an operation it is less obvious where something that might be expensive is being done. In general, I prefer APIs that makes the cheap simple and the expensive clearly so.
Using getters and setters has been my policy for exactly the reasons @jacg gives. Unfortunately, I've run into borrow check errors with Rust. I'm hoping Polonius will fix them.
If we're talking about the function-call vs. field-access distinction, I fully expect compilers (of languages where performance is frequently a major concern) to inline (or even completely eliminate) some function calls, so seeing a syntactic function call tells me nothing whatsoever about the cost of the operation. This would leave us (in a language without a property-like mechanism) with the distinction between the cost of
So I don't find this distinction useful at all.
Getting back on-topic, I do find that the difference between things like .i
and .into()
or .x
and .get_x()
can be significant for the signal-to-noise ratio of the code, though not necessarily in a positive way. In the specific case of .i
vs .into()
in Rust, I suspect that .into()
is the better choice.
Whereas being able to change your mind about compute-vs-store (etc.) implementation details without breaking client code, is a huge boon. Even though I would never claim that 'all those millions of Java and C++ programmers can't be wrong' I do believe that the all-data-private rule is the usually the right choice in those languages.
The borrow checker is not allowed to use information about function bodies, only signatures, when checking a call to the function — that's a rule about what is public versus implementation detail. Polonius will not change that.
This is good rule but not because if mythical ability to switch from representation of something as field convert to something else than a field.
My take on the whole affair is very different, which, incidentally, makes borrow checker happy without any need for Polonius.
Rule is simple: if some field can be changed to arbitrary value without touching other fields - then what I have before me is not an object, it a struct with a few fields and these fields should be public. No need for getters and setters.
If field can not be changed to arbitrary value because there are certain invariants which must be keep unbroken — then I have an object, not a collection of fields and this fact makes setter pointless: if field can not be just changed by itself, then I wouldn't have any setters because they would be dangerous, I would have functions which change few fields at the same time.
Getters still may make sense in this case, and yes, they would be functions, but there are no problem with that since you can easily borrow the same object many times if you only need a read-only access.
Now, what about that ability to change from field to caculatable function? I wouldn't say that this may not ever happen. But in practice this happens so rarely (about 1 type out of 100 needs this conversion in it's lifetime in my experience) that if that is really needed… I just accept the need to change all the places where this structure is used.
But yes, this approach requires some planning upfront and understanding of what exactly you want to implement. Not random shuffling of data and code around with help of IDE.
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.