Skip to content

Lint suggestion: implement ExactSizeIterator when size_hint() delegates to something implementing ExactSizeIterator #17393

Description

@DanielEScherzer

What it does

Iterators are often created that iterate over some contained inner collection, or have a size that is known based on a range. I ran into this when building iterators for FFI types that use the struct hack, but this also occurs elsewhere, e.g. the proc_macro::token_stream::IntoIter type is a wrapper around https://doc.rust-lang.org/std/vec/struct.IntoIter.html, but while the latter is marked as ExactSizeIterator, the wrapper isn't

Advantage

Those unfamiliar with ExactSizeIterator or who forgot to add it are prompted to add an implementation, and get the benefits of that trait

Drawbacks

If the ExactSizeIterator isn't going to be useful this is just another warning/suggestion

Example

From https://github.com/rust-lang/rust/blob/3664b37017c529cad0f0ed259769f15743c21a6a/library/proc_macro/src/lib.rs#L532

#[derive(Clone)]
pub struct IntoIter(
    std::vec::IntoIter<
        bridge::TokenTree<
            bridge::client::TokenStream,
            bridge::client::Span,
            bridge::client::Symbol,
        >,
    >,
);

impl Iterator for IntoIter {
    type Item = TokenTree;

    fn next(&mut self) -> Option<TokenTree> {
        // Elided, not applicable to this suggestion
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        self.0.size_hint()
    }

    fn count(self) -> usize {
        self.0.count()
    }
}

Could be written as:

// All of the code above, and also
impl ExactSizeIterator for IntoIter {}

Comparison with existing lints

clippy::should_implement_trait suggests existing methods that should be moved to trait implementations, because the code has already been written - here, the suggested addition of the ExactSizeIterator doesn't require moving any code around, just adding another line

Additional Context

Simplified version of the code that motivated this:

#![allow(non_camel_case_types)]
use std::ops::Range;

#[repr(C)]
#[derive(Debug, Copy, Clone)]
struct zend_attribute_arg {
    // Stub for demo
    value: u32,
}

#[repr(C)]
struct zend_attribute {
    argc: u32,
    args: [zend_attribute_arg; 1],
}

impl zend_attribute {
    fn args_iter(&self) -> ZendAttributeArgIter<'_> {
        ZendAttributeArgIter {
            raw: self,
            range: 0..(self.argc as usize),
        }
    }
}

struct ZendAttributeArgIter<'a> {
    raw: &'a zend_attribute,
    range: Range<usize>,
}

impl Iterator for ZendAttributeArgIter<'_> {
    type Item = zend_attribute_arg;

    fn next(&mut self) -> Option<Self::Item> {
        self.range
            .next()
            .map(|i| unsafe { *self.raw.args.as_ptr().add(i) })
    }
    fn size_hint(&self) -> (usize, Option<usize>) {
        self.range.size_hint()
    }
}

fn main() {
    let args = [
        zend_attribute_arg { value: 5 },
        zend_attribute_arg { value: 10 },
        zend_attribute_arg { value: 1 },
    ];
    let attrib = zend_attribute {
        argc: args.len().try_into().unwrap(),
        args: unsafe { *args.as_ptr().cast() },
    };
    for arg in attrib.args_iter() {
        println!("{arg:?}");
    }
}

Metadata

Metadata

Labels

A-lintArea: New lints

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions