Plus impl is also an item, not an expression, and contextual keywords work much better there than they do in other places. (See how union works fine, despite being contextual, and that's a more difficult case than partial impl would be.)
With the last considerations about how partial impl blocks should be identified, i think it's enough to close this question with the result being partial impl blocks should be prefixed with partial.
Now, the only question that should stay opened for a while is about the feature scope.
Should it be:
crate-scoped: A partial impl block for a given trait could be anywhere in the crate file structure.
module-scoped: All partial impl blocks for a given pair (Trait,Struct) should be in the same module where the struct is defined.
file-scoped: All partial impl blocks for a given pair (Trait,Struct) should be in the same file where the trait is defined.
By now, we have something like this for the simplest real-case usage of this feature:
For the same structure described in my last reply:
root/
src/
module/
mod.rs // Trait and struct defined here for the example
core_impl.rs // Core implementation
windows_impl.rs // Windows Specific Code
linux_impl.rs // Linux Specific Code
feature_1_impl.rs // Feature 1 Specific Code
feature_2_impl.rs // Feature 2 Specific Code
// ...
main.rs
cargo.toml
// ...
mod.rs:
pub trait SomeTrait {
// Dozens Of methods, but honestly, this is not the problem even considering default implementations.
}
pub struct SomeStruct {
}
pub mod core_impl;
#[cfg(target_os="windows")]
pub mod windows_impl;
#[cfg(target_os="linux")]
pub mod linux_impl;
#[cfg(feature="feature_1")]
pub mod feature_1_impl;
#[cfg(feature="feature_2")]
pub mod feature_2_impl;
core_methods.rs:
use crate::module::SomeTrait;
use crate::module::SomeStruct;
partial impl SomeTrait for SomeStruct {
fn core_method_1() -> () {};
fn core_method_2() -> () {};
fn core_method_N() -> () {};
}
windows_impl.rs:
use crate::module::SomeTrait;
use crate::module::SomeStruct;
// This CFG is redundant, but could exist for documentation or style purposes.
#[cfg(target_os="windows")]
partial impl SomeTrait for SomeStruct {
fn platform_specific_method_1() -> () {};
fn platform_specific_method_2() -> () {};
fn platform_specific_method_N() -> () {};
}
linux_impl.rs:
use crate::module::SomeTrait;
use crate::module::SomeStruct;
#[cfg(target_os="linux")]
partial impl SomeTrait for SomeStruct {
fn platform_specific_method_1() -> () {};
fn platform_specific_method_2() -> () {};
fn platform_specific_method_N() -> () {};
}
feature_1_impl.rs
use crate::module::SomeTrait;
use crate::module::SomeStruct;
#[cfg(feature="feature_1")]
partial impl SomeTrait for SomeStruct {
fn feature_1_specific_method_1() -> () {};
fn feature_1_specific_method_2() -> () {};
fn feature_1_specific_method_N() -> () {};
}
feature_2_impl.rs
use crate::module::SomeTrait;
use crate::module::SomeStruct;
#[cfg(feature="feature_2")]
partial impl SomeTrait for SomeStruct {
fn feature_2_specific_method_1() -> () {};
fn feature_2_specific_method_2() -> () {};
fn feature_2_specific_method_N() -> () {};
}
Once this question is solved. I'll update the RFC text in #1 to include the result of the recent discussion about once in a week. But for now. Merry Christmas for everyone!
Itâs always easier to loosen than tighten, so I wonât immediately argue âcrate!â But why the trait and not the struct? The struct is the one with private information that might be used in the impl.
Sorry for that typo, i'll update it to be in the same module as the struct definition.
I don't think we have anything that's file-scoped today, so probably wouldn't want to pick that one.
FWIW I would find it confusing to require partial in the case of trait impl blocks, but not for inherent ones, and would wager that that would trip up learners of the language.
A clarification in the docs that all impl blocks can be split up would suffice IMHO. One can always simple add a comment that an impl block is incomplete for documentation of such cases. For how to locate actual implementations, I would leave that to IDEs.
To be clear, another question that's still open is "should we do this at all". That's a question about whether the benefits outweigh the drawbacks.
That said:
If we did this at all, I think it makes sense to be crate-scoped.
I have updated the first post to include this session.
Not only that, but the first post is currently reflecting the actual state of the proposal with proper examples.
One option I would like to see discussed is to allow multiple impl blocks for trait implementations, but still require all functions to be present for documentation purposes, and annotate individual functions instead. E.g. (stupid naming to avoid bikeshedding):
In common.rs:
use crate::module::SomeTrait;
use crate::module::SomeStruct;
impl SomeTrait for SomeStruct {
fn core_method_1() -> () { ... };
fn core_method_2() -> () { ... };
fn core_method_N() -> () { ... };
#[this_fn_is_in_another_impl]
fn platform_specific_method_1() -> ();
#[this_fn_is_in_another_impl]
fn platform_specific_method_2() -> ();
#[this_fn_is_in_another_impl]
fn platform_specific_method_N() -> ();
}
In a platform_specific file, e.g. windows_impl.rs:
use crate::module::SomeTrait;
use crate::module::SomeStruct;
#[cfg(target_os="windows")]
impl SomeTrait for SomeStruct {
#[this_fn_is_in_another_impl]
fn core_method_1() -> ();
#[this_fn_is_in_another_impl]
fn core_method_2() -> ();
#[this_fn_is_in_another_impl]
fn core_method_N() -> ();
fn platform_specific_method_1() -> () { ... };
fn platform_specific_method_2() -> () { ... };
fn platform_specific_method_N() -> () { ... };
}
Could you elaborate a bit more on this idea? Right now it feels simpler and more robust to keep the documentation as the responsibility of the trait definition itself.
In this model, the trait is the canonical place for docs and for the âpublic contractâ. Requiring partial impl blocks to repeat method signatures without bodies across multiple files would introduce a lot of duplication for the same (Trait, Type) pair, especially when there are many methods and they are split across modules/platform-specific files. That duplication can also drift over time and becomes a maintenance tax, while not really improving the API docs beyond what the trait already provides.
/// Docs are placed here
pub trait ExampleMethods {
/// Or here
fn method_1() -> ();
/// Or even here
fn method_2() -> ();
/// Maybe Here
fn macro_method_1() -> ();
}
pub struct Example {}
macro_rules! generate_macro_method_1 {
() => {
partial impl ExampleMethods for Example {
fn macro_method_1() -> {}
}
}
}
generate_macro_method_1!();
// core method
partial impl ExampleMethods for Example {
fn method_1() -> () {}
}
// These two could have their own file depending on the complexity.
#[cfg(target_os="windows")]
partial impl ExampleMethods for Example {
fn method_2() -> () {}
}
#[cfg(target_os="linux")]
partial impl ExampleMethods for Example {
fn method_2() -> () {}
}
That said, your concern about navigation through these blocks is totally fair. If partial impl blocks can be scattered across the codebase, itâs easy to become overly dependent on tooling/IDE support to navigate between them. This is exactly why I listed âtooling dependency / navigationâ as a drawback for the implementation in the latest update of my first post.
If the ecosystem don't give support for this feature, it would become more a problem than a solution, even with the idea of yours.
One thing I realized is that, today, without dedicated language or tooling support, the only practical way to work with this would be to rely heavily on âgo to definitionâ / âgo to implementationâ (e.g. Ctrl+Click) and keep the trait definition open as the main reference point. But thatâs more of a workaround than a real solution, and ideally something the language and tooling should support more explicitly.
I suggested it not so much for the purpose of API documentation as such, but for internal documentation, as a safety measure for precisely when the API changes: explicitly forcing the implementer to touch every impl block.
Also for the reader of an impl block to be able to see the other trait functions without having to refer to the trait definition in another file.
(FWIW, ideally I'd also like every default fn mentioned to make sure the implementer follows whether the default or override is used, but that would prevent adding default fns in a backwards-compatible manner).
Now, whether or not it'd really add all that much, or is more maintenance overhead that it's worth, I'm honestly not sure.
I think that, instead of this, I might just say it should be done in one impl via delegation instead. You could have one impl block for the trait, but do something like fn core_method_1 = windows_core_method_1; where that method is a private inherent impl you defined somewhere else.
See also this discussion: