Rust for enterprise grade systems?

This is design that became de-facto standard for every enterprise solution.

How would you design this in rust?

language = java

interface Collection {
    int size();
     // Iterator does only one thing, iterate.
     // Question about clean, please do not use Iterator::size like it available in rust.
    Iterator iterator();
}

abstract class AbstractCollection implements Collection {
   @Override
   public int size() {
      int size = 0;
      var iterator = iterator();
      while(iterator.hasNext()) { 
          size++;
          iterator.next();
      }
      return size;
   }
}

class ArrayList extends AbstractCollection {
    Object[] array;
 
    @Override
    public int size() {
       return array.length
    }

    @Override
    Iterator iterator() {
         ...
    }
}


class LinkedList extends AbstractCollection {
     Node header;

    // Assuming that linked list doesn't implement size, the real on in JDK does.
    // But in this example, super::size from AbstractCollection is used.

    @Override
    Iterator iterator() {
         ...
    }
}

UPD:

Let's define the problem a little bit more formally:

You need to implement library for collection.

  1. On base level you need a interface, that would define set of operation, for simplicity let's go with two operations.
    • Iterator and size
  2. Than in most generic way you can just iterator is count how many items you have, but it is slow and optimal.
  3. If you know that you concrete implementation Array, you can just use property of array.
  4. Let's say you have another implementation, which implements methods like Add and Remove - you can bring counter into here, however if we already know that it is Array than counter obviously is redundant.

UPD:

  • Add LinkedList into example above.

UPD:

More context on problem:

And some extended code examples:

UPD:

Example of OOP with inheritance in Linux kernel:

https://www.reddit.com/r/rust/comments/1wrp7mc/comment/pczoamg/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button

static struct ata_port_operations ali_early_port_ops = {
    .inherits      = &ata_sff_port_ops,
    .cable_detect  = ata_cable_40wire,
    .set_piomode   = ali_set_piomode,
    .sff_data_xfer = ata_sff_data_xfer32,
};

and

static void ata_finalize_port_ops(struct ata_port_operations *ops)
{
	static DEFINE_SPINLOCK(lock);
	const struct ata_port_operations *cur;
	void **begin = (void **)ops;
	void **end = (void **)&ops->inherits;
	void **pp;

	if (!ops || !ops->inherits)
		return;

	spin_lock(&lock);

	for (cur = ops->inherits; cur; cur = cur->inherits) {
		void **inherit = (void **)cur;

		for (pp = begin; pp < end; pp++, inherit++)
			if (!*pp)
				*pp = *inherit;
	}

	for (pp = begin; pp < end; pp++)
		if (IS_ERR(*pp))
			*pp = NULL;

	ops->inherits = NULL;

	spin_unlock(&lock);
}

Exact explanation in comment on reddit.

The standard library has no trait abstracting over collections (I haven't missed it tbh and I have used Rust for enterprise software development). You could use a third party library like trait-based-collection for this or implement their Collection trait yourself for all collections you want to support.

You wouldn't.
You have IntoIterator and ExactSizeIterator in the standard library, which would cover both methods of the interface.

That being said, what does this have to do with enterprise grade systems?
Do you mean "enterprise grade" as defined by Kevlin Henney?

I'd say that's not working the way it's supposed to...

...but maybe that's not so unusual when it comes to enterprise. :rofl:

Make me think of my favourite enterprise Java example

I am not sure what happens there in size method, but it seems that we're interatring over the whole collection to get its size)

The problem is iterator doesn't have to be finite.

In Rust, we have iterators: std::collections - Rust
Some of them know their size: ExactSizeIterator in std::iter - Rust
Some do not.

If you really wanna be polymorphic and type-agnostic, code against iterators:

fn do_all<T: Display>(i: impl ExactSizeIterator<Item = T>) {
    print!("I am {} long", i.len());
    for x in i {
        print!("{x}");
    }
}

fn main() {
    do_all(vec![1u8, 2u8, 3u8].iter());

    let mut s: HashSet<bool> = HashSet::new();
    s.insert(true);
    do_all(s.iter());
}

But since most of the time you use vectors and arrays, you can use slices which also have len

fn do_all<T: Display>(i: &[T]) {
    print!("I am {} long", i.len());
    for x in i {
        print!("{x}");
    }
}

fn main() {
    do_all(&vec![1u8, 2u8, 3u8]);
    do_all(&[1]);
}

main/java/com/seriouscompany/business/java/fizzbuzz/packagenamingpackage, 8 nested directories with no other contents :ferris_melt:
(there's likely worse things about it, but I couldn't find any actual code in the sea of boilerplate)

yes, you right. just updated the example, but problem still stays.

Thank you for pointing out.

Fix once - run everywhere.

ExactSizeIterator is trait of iterator.

However to know the size of collection, you may not need iterator at all.

The question mainly how would you implement size for different types of collection, where you have different level of inheritance.

I've tried to watch video, however 1 hours of video content, I think I didn't grasp the idea there, I thing I love USS enterprise and everything happened in this series that mentioned in video, you know blueprint of vessel they provided, you looks fascinated word that Spock often used.

Let's talks closer about the problem, ExactSizeIterator is trait of iterator.

But question was about collection, not about iterator, sometime we could know size of collection, even if doesn't implements iterator.

There is no such abstraction as "collection" in Rust. Collection is something very JVM-specific (though, it exists in .NET as well).

In Rust (much like in C++) the abstraction is an iterator. This is because the only thing you could do with general collection is iterate over it.

Are you sure you can't use iterators instead of collections?

sometime we could know size of collection, even if doesn't implements iterator.

I believe it should be possible to implement iterator for every single collection (if we mean data structure), size is probably also make sense for all of them (but iterators, of course, might have no size, be lazy or even infinite)

We don't quite need to dispatch over Vec::len, [T]::len, BTreeSet::len, etc.

fn collection_len<T>(collection: T) -> usize
where
    T: IntoIterator,
    <T as IntoIterator>::IntoIter: ExactSizeIterator
{
    collection.into_iter().len()
}

fn main() {
    assert_eq!(collection_len(&[1, 2, 3]), 3);
    assert_eq!(collection_len(&vec![1, 2, 3]), 3);
    assert_eq!(collection_len([1, 2, 3]), 3);
    assert_eq!(collection_len(vec![1, 2, 3]), 3);
    
    let m = std::collections::BTreeSet::from([0, 1]);
    assert_eq!(collection_len(&m), 2);
    assert_eq!(collection_len(m), 2);
}

However, I haven't had a usage for collection_len in my crates nor seen one in others'.

I believe it should be possible to implement iterator for every single collection

What about a collection stored on a very weird flash (and likely hypothetical) chip that can be written to at any time, and where the size is known, but it cannot be read from after writing until the computer is restarted?

How would you use collection_len method if your collection doesn't implement iterator with ExactSizeIterator?


By the way, here is few experimentation I did in C++:

template <typename Collection>
std::size_t size(const Collection& collection) {
    std::size_t count = 0;

    for (auto it = collection.begin(); it != collection.end(); ++it) {
        ++count;
    }

    return count;
} 

And then in same file I can put this:

template <typename T, typename Allocator>
std::size_t collection_size(const std::vector<T, Allocator>& c) {
    return c.size();
}

template <typename T, std::size_t N>
std::size_t collection_size(const std::array<T, N>&) {
    return N;
}

And if I want to have something really hacky, I can even put this in the same file:

// Any collection having size()
template <typename Collection>
std::size_t collection_size(const Collection& collection)
    requires requires { collection.size(); }
{
    return collection.size();
}

And then compiler using method overloading mechanism will chose the best specialized implementation for collection_size.

I wouldn't call such a chip a collection, because if you can't read it (neither sequentally nor randomly) but only write to it, it is probably a Sink, but not a collection.

Iterators really should implement ExactSizeIterator if they have a known size, which includes iterators over all normal collections. An iterator that does not implement it, for example, is a open integer range.

When I am saying

if doesn't implements iterator.

In good design, we typically trying to strip implementation details as much as we can.

I think I see you point when you speak about concrete collection, and most of them iterable, I guess I can't bring from my head, any collection where I can't make iterator.

However it is more hypothetical question, how we dealing with abstractions, when we want to build some code that will depend on least necessary knowledge. OOP quite helpful for that, because base class has limitted knowledge about concrete implementation, however perhaps OOP is not the only way to do so.

If we back to concrete cases:

if doesn't implements iterator.

When we speak about ArrayList, ArrayList not necessary has to implement iterator to know the size, even that you can, and what is more important, using iterator for that is not optimal.


And by the way, iterator as interface never was intended to provide size, it is intended to iterate, if the only generic way is to use very specific version of iterator, it is example of bad design.


Some experimentation for same problem in C++;

template <typename Collection>
std::size_t size(const Collection& collection) {
    std::size_t count = 0;

    for (auto it = collection.begin(); it != collection.end(); ++it) {
        ++count;
    }

    return count;
} 

template <typename T, typename Allocator>
std::size_t collection_size(const std::vector<T, Allocator>& c) {
    return c.size();
}

template <typename T, std::size_t N>
std::size_t collection_size(const std::array<T, N>&) {
    return N;
}

// Any collection having size()
template <typename Collection>
std::size_t collection_size(const Collection& collection)
    requires requires { collection.size(); }
{
    return collection.size();
}

If it take std::vector as concrete example here, at least 3 methods from the list above will be fit to handle vector, but compiler using method overloading mechanism will chose the most concrete one.

If someone called something enterprise it doesn't make it so yet.

I am not interested about examples of bad design.

If you have example of good, please present, I will be very curious to see.

I think we should define a "collection" then. Why do we call a "collection" exactly?

ArrayList provides indexed access, and if it also reports its size, it should be trivial to implement an iterator, simply outside of the class.

In your C++ code you are literally using iterator:

Returns an iterator to the beginning of the given range.

So, in C++ (and in Rust) the abstraction is "iterator", not a "collection".

The trick you are doing with templates (I mean, you fallback to manual iteration if size doesn't exist) is probably not available in plain Rust (but you can probably write a macros, give it a try!)

In C++ I am using iterator only in one method if four, in three another methods I am not using iterator.