#query #collection #iterator

linq_rs

A LINQ-like query library for Rust iterators — no external dependencies

1 unstable release

0.1.0 Mar 28, 2026

#2230 in Algorithms

MIT license

63KB
1K SLoC

linq_rs

A LINQ-style query library for Rust — zero external dependencies.

Brings the full power of C# LINQ to Rust iterators as lazy, composable extension methods.


Quick Start

use linq_rs::LinqExt;

let result: Vec<_> = vec![1, 2, 3, 4, 5, 6]
    .into_iter()
    .where_(|x| x % 2 == 0)   // filter
    .select(|x| x * x)         // project
    .to_vec();

assert_eq!(result, [4, 16, 36]);

Add to Cargo.toml:

[dependencies]
linq_rs = { path = "." }

API Reference

Filtering

Rust (linq_rs) C# LINQ equivalent
where_(predicate) Where(predicate)

Projection

Rust C#
select(f) Select(f)
select_many(f) SelectMany(f)
flatten_() SelectMany(x => x)

Paging / Slicing

Rust C#
skip(n) Skip(n)
skip_while_(p) SkipWhile(p)
take_(n) Take(n)
take_while_(p) TakeWhile(p)
chunk(size) Chunk(size)

Set Operations

Rust C#
distinct() Distinct()
distinct_by(key_fn) DistinctBy(key_fn)
except(other) Except(other)
intersect(other) Intersect(other)
union_(other) Union(other)
concat_(other) Concat(other)

Ordering

Rust C#
order_by(key_fn) OrderBy(key_fn)
order_by_descending(key_fn) OrderByDescending(key_fn)
.then_by(key_fn) .ThenBy(key_fn)
.then_by_descending(key_fn) .ThenByDescending(key_fn)
reverse() Reverse()

Aggregation

Rust C#
aggregate(seed, f) Aggregate(seed, f)
sum_() Sum()
count_where(p) Count(p)
min_() Min()
max_() Max()
min_by_key_(key_fn) MinBy(key_fn)
max_by_key_(key_fn) MaxBy(key_fn)
average(selector) Average(selector)

Element Operations

Rust C#
first_or_default() FirstOrDefault()
first_where(p) FirstOrDefault(p)
last_or_default() LastOrDefault()
last_where(p) LastOrDefault(p)
element_at(index) ElementAtOrDefault(index)
single_or_default() SingleOrDefault()

Quantifiers

Rust C#
any_(p) Any(p)
all_(p) All(p)
contains_(value) Contains(val)
is_empty_() !Any()

Joining

Rust C#
join(inner, outerKey, innerKey, resultSel) Join(inner, ok, ik, rs)
group_join(inner, outerKey, innerKey, rs) GroupJoin(inner, ok, ik, rs)

Grouping

Rust C#
group_by(key_fn) GroupBy(key_fn)

Returns an iterator of [Grouping<K, T>] — each item has a .key and .elements.

Conversion

Rust C#
to_vec() ToList()
to_hashmap(key_fn) ToDictionary(key_fn)
to_hashset() ToHashSet()
to_lookup(key_fn) ToLookup(key_fn)

Utility

Rust C#
zip_(other, result_sel) Zip(other, resultSelector)
append_item(item) Append(item)
prepend_item(item) Prepend(item)
for_each_(action) ForEach(action)
sequence_equal(other) SequenceEqual(other)

Realistic Example

use linq_rs::{LinqExt, ThenBy};

#[derive(Clone)]
struct Employee { name: &'static str, dept: &'static str, salary: u32 }

let employees = vec![
    Employee { name: "Alice",  dept: "Eng",   salary: 120_000 },
    Employee { name: "Bob",    dept: "Eng",   salary: 95_000  },
    Employee { name: "Carol",  dept: "Sales", salary: 80_000  },
    Employee { name: "Eve",    dept: "Eng",   salary: 130_000 },
];

// High-earners per dept, sorted by dept then salary descending
let result: Vec<_> = employees
    .into_iter()
    .where_(|e| e.salary > 90_000)
    .order_by(|e| e.dept)
    .then_by_descending(|e| e.salary)
    .into_iter()
    .select(|e| (e.dept, e.name, e.salary))
    .to_vec();

// [("Eng", "Eve", 130000), ("Eng", "Alice", 120000), ("Eng", "Bob", 95000)]

Group Join (Left Outer Join)

use linq_rs::LinqExt;

let depts  = vec![(1u32, "Eng"), (2, "Sales")];
let emps   = vec![(1u32, "Alice"), (1, "Bob"), (2, "Carol")];

let result: Vec<_> = depts.into_iter().group_join(
    emps,
    |(id, _)| *id,
    |(dept_id, _)| *dept_id,
    |(_, dept), members| {
        let names: Vec<_> = members.into_iter().map(|(_, n)| n).collect();
        format!("{dept}: {}", names.join(", "))
    },
).collect();

// ["Eng: Alice, Bob", "Sales: Carol"]

Lookup

use linq_rs::LinqExt;

let data = vec![("fruit", "apple"), ("veggie", "carrot"), ("fruit", "banana")];
let lookup = data.into_iter().to_lookup(|(cat, _)| *cat);

assert_eq!(lookup.get(&"fruit"), &[("fruit", "apple"), ("fruit", "banana")]);

Design Notes

  • Lazy by default — filtering, projection, and slicing adaptors are lazy iterators; no allocation happens until you collect() or iterate.
  • Eager where necessaryorder_by, reverse, distinct, set operations, and joins must buffer the sequence. This mirrors C# LINQ's behaviour.
  • Zero dependencies — only std.
  • Naming — methods that shadow Rust keywords or std trait methods are suffixed with _ (where_, take_, any_, etc.).

No runtime deps