I just created an example shortening it to .i(), when I re-read your question asking to shorten it to i (without parenthesis). Anyway, maybe other readers are interested in how it works with .i(), so I'm sharing my already made up example code:
mod short_into {
pub trait IntoExt<T>: Sized
where
Self: Into<T>,
{
fn i(self) -> T {
self.into()
}
}
impl<T, U: Into<T>> IntoExt<T> for U {}
}
use short_into::IntoExt;
fn main() {
const N: i16 = 5;
let n: i32 = N.i();
println!("Hello {}", n);
}
I tried to implement CustomInitialize and CustomAssign trait in both my toy crate and this forum, which allow using
fn main() {cai!{// Custom Assign and Initialize, or "菜" in Chinese which suggest my programming skill is not good enough.
let a:i32:=1u8;
let mut b=1i32;
b~a;
b:=a;
}}
by define the corresponding trait correctly.
no need to write Neg trait which actually do not support convert the base type to different types.
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;
}
}
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.
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:
Pattern matching, destructuring
Partial moves out of owned structures
Simultaneous borrows of different parts of a structure
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
a field access,
absolutely anything at all.
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.