-
-
Notifications
You must be signed in to change notification settings - Fork 217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The auto-borrowing system in the context of filters accepting params by value #550
Comments
I'm mainly just referencing stuff from the two PRs you linked. The main reason to prefer pass-by-value compared to pass-by-reference. Is that given you can call Additionally, if everything is auto-borrowed, then as soon as you throw local variables into the mix. Then soon you end up with See also #392. |
It might make sense to have |
Something makes me unhappy that these are not interchangeable in a suitable environment:
And likewise,
But I'm really sympathetic to the issues raised in the linked issues, particularly the use of iterator methods raised by @clouds56. So I understand the appeal of dropping auto-borrows in some cases. I'm not really sympathetic to concerns about literals, myself. if
Well, there is always
I've never used macros and so I definitely don't understand the design issues there. But why would calling the macro add a level of reference? If all the code is generated into the same scope, wouldn't it be able to assume everything that needs to be borrowed is already a reference?
I think this might be right. I'm coming back around to the idea that the filters should take references and everything should be a reference, even if it's a |
The main issue with using Changing Changing it to taking In regards to pub fn abs<T, B>(number: B) -> Result<T>
where
T: Signed,
B: Borrow<T>,
{
Ok(number.borrow().abs())
} Then ideally be able to keep all your examples as is, and never need to do It would also not solve the issue in relation to If pub fn abs<B>(number: B) -> Result<i32>
where
B: Borrow<i32>,
{
Ok(number.borrow().abs())
} Then all examples would work. So for any filter or function taking a concrete type, then it might be worth using |
Maybe we can come up with a local trait |
It's not possible to have multiple blanket impls like that. Since I recall it being possible to do some magic with specialization, but yeah, specialization is still unstable. Related comment: #376 (comment) |
Post is a bit long, my apologies. Please stick with it, I get somewhere interesting by the end :). I've thought a bit more about this and something occurred to me: in Rust, there is one and only one idiom for "this can take an owned or borrowed thing and you don't need to care which": method call syntax. let owned = 42;
let borrowed = &owned;
assert_eq!(owned.abs(), borrowed.abs()) This kind of suggests that there just shouldn't be a filter like So I've now completely reversed positions from my initial post -- I now think that all filters should take their (first) argument by reference. But what about the extra parameters like on the filters Now we have a weird mis-match, where the first argument of a filter (passed in through the pipe) is always by reference but subsequent arguments are by value. When we're passing literals or function call results into a filter, we'd really like them to be auto-borrowed for us. When we're passing #[derive(Template)]
struct Example {
num: isize,
text: String,
object: Object,
}
struct Object;
impl Object {
fn method(&self) -> isize { todo!() }
fn munge(&self, other: &isize) -> isize { todo!() }
} We want to write:
And also:
And also:
And I believe this system then implies that function calls that accept reference are now:
This all leads me to believe that the thing which determines whether we should be auto-borrowing is not whether it's a literal, call, or template field, but rather, how you're using the value. Aside: should the filter syntax codegen to method call syntax? They kind of already look like methods if you squint. You could use any method in scope, and implement custom filters as traits. So Rusty! My oh my that would be a huge breaking change, though. |
This is not true. There is a second one directly in the standard library, namely all macros that work with format, like let s: String = "foo".to_owned();
println("{}", s);
assert_eq!(s.as_str(), "foo"); // s cannot be used anymore However, using macros from the standard library, this works, and further, also if we take a reference: let s: String = "foo".to_owned();
println!("{}", s); // works, doesn't consume s
println!("{}", &s); // does exactly the same as above
assert_eq!(s.as_str(), "foo"); // s can still be used Since askama is a macro, it is IMHO not only idiomatic to have it accept both references and owned values, but also in line with the macros implemented in the standard library. |
Interesting angle, @msrd0. Though askama is a macro, it emits a trait implementation, so the code it's lowered to has to play by the rules of functions, not macros. I think the discussion above suggests that there's not really a way to emit code that will handle the distinction without intervention on the part of the template author. If you've got a suggestion of how to implement the more flexible behavior please mention it! |
Sure. Just looking at the expansion of above code, I was able to reproduce the idea behind it (Playground): fn strlen(s: &str) -> usize {
s.len()
}
macro_rules! strlen {
($s:expr) => {
match (&$s,) {
s => strlen(s.0),
}
};
}
fn main() {
let s: String = "foo".to_owned();
println!("{}", strlen!(s));
println!("{}", strlen!(&s));
} So askama could (if it had a filter |
That's a nice idea @msrd0, and seems to go to the idea that all filters should be written to take their argument by reference (since that pattern will not compile if the function takes a value). On another note, how about the result of a filter? Looking into the code, I noticed that after #423, this no longer compiles: |
After reading the conversations on #423 and #492 I'm convinced that filters that take
Copy
types should heceforth accept their parameters by value. This makes filters very nicely line up with general API design ofCopy
types.Passing function call return values and literals is exactly right:
{{ 1|abs }}
or{{ list.len()|abs }}
.There's just one thing: the very common case of passing template variables is now burdened by an apparently-unnecessary
.clone()
. What was previously the perfectly clear{{ field|filter }}
is now (with the API change to take by value){{ field.clone()|filter }}
.I'm not sure what the right solution is: obviously the auto-borrow is very useful and necessary in most cases. This wrinkle doesn't seem significant enough to justify introducing
*
and&
. I don't think codegen-time has visibility into trait impls. Maybe all filters should just continue to accept their arguments by reference as a general practice? (What ofabs
, then?)The text was updated successfully, but these errors were encountered: