vak: (Знайка)
[personal profile] vak
Продолжаю двигаться по списку: изучаю базовые примитивы языка Rust. Сегодня рассмотрим Cell<T>, RefCell<T> и, на десерт, Option<T>.

Cell это простой контейнер на одно значение произвольного типа. С одной хитрой фичей: если значение неизменяемое (то есть имеет атрибут const), Cell позволяет обходить это ограничение и всё-таки изменять его. Аналогично ключевому слову "mutable" в Си++. Спросим у grok.com.

Question: Please show me how type Cell<T> can be implemented in C++.

Let’s dive into how we might implement Rust’s Cell<T> in C++. Rust’s Cell<T> is a fascinating type - it provides interior mutability, allowing you to mutate its contents even when you only have a shared reference, all while being safe in a single-threaded context. It’s lightweight, with no runtime overhead beyond the mutation itself, and it doesn’t involve locks since it’s not thread-safe (that’s what RefCell or atomics are for in Rust). In C++, we don’t have the exact same ownership and borrowing rules, so we’ll need to adapt the concept while preserving its spirit.

The core idea of Cell<T> is simple: it wraps a value of type T and provides methods to get and set that value, bypassing C++’s usual const-correctness restrictions in a controlled way. Rust uses it to allow mutation through shared references, so in C++, we’ll aim for something that lets us modify a value even when we might otherwise be constrained, like through a const reference, while keeping the implementation straightforward.

Here’s a basic implementation in C++: cell.cpp.
#include <utility>
#include <iostream>

template <typename T>
class Cell {
private:
mutable T value; // Mutable to allow mutation even when Cell is const

public:
// Constructor
explicit Cell(T initial) : value(std::move(initial)) {}

// Get a copy of the value
T get() const {
return value;
}

// Set a new value
void set(T new_value) const {
value = std::move(new_value);
}

// Replace the value and return the old one
T replace(T new_value) const {
T old_value = std::move(value);
value = std::move(new_value);
return old_value;
}
};

Notes

  • Constness in C++: In Rust, Cell<T> shines because it subverts the immutability of shared references. In C++, if you make a Cell const, set and replace won’t work without some trickery. Rust’s Cell uses its internal implementation to allow mutation through a shared reference, but in C++, const objects are immutable unless you use mutable or const_cast. By making the value member mutable, we allow the Cell to modify its contents even when the Cell object itself is const, mimicking how Rust permits mutation through a shared reference.
  • Using mutable here lets Cell behave more like Rust’s version, where the container’s constness doesn’t prevent internal mutation. This is closer to “interior mutability,” but it’s a bit of a cheat in C++ - use it carefully, as it might surprise other developers expecting const to mean full immutability.
  • Move-Only Types: Rust’s Cell<T> works with any T, even non-copyable types, since get requires Copy but set and replace don’t. In C++, if T is move-only (e.g., std::unique_ptr), get() as written above won’t compile. You could add a std::enable_if to disable get() for non-copyable types or provide a separate get_ref() method returning const T&.

Example usage

int main() {
// Non-const Cell
Cell<int> cell(42);
std::cout << "Initial value: " << cell.get() << "\n"; // Prints 42

cell.set(100);
std::cout << "After set: " << cell.get() << "\n"; // Prints 100

int old = cell.replace(200);
std::cout << "Old value: " << old << "\n"; // Prints 100
std::cout << "New value: " << cell.get() << "\n"; // Prints 200

// Const Cell
const Cell<int> const_cell(300);
std::cout << "Const cell initial value: " << const_cell.get() << "\n"; // Prints 300

const_cell.set(400); // Works despite constness!
std::cout << "Const cell after set: " << const_cell.get() << "\n"; // Prints 400

int old_const = const_cell.replace(500);
std::cout << "Const cell old value: " << old_const << "\n"; // Prints 400
std::cout << "Const cell new value: " << const_cell.get() << "\n"; // Prints 500

return 0;
}
Running this code will produce:
Initial value: 42
After set: 100
Old value: 100
New value: 200
Const cell initial value: 300
Const cell after set: 400
Const cell old value: 400
Const cell new value: 500

Why This Works

  • The mutable keyword tells C++ that value can be modified even in a const context. This is the key to achieving “interior mutability” in C++, akin to how Rust’s Cell<T> bypasses reference immutability.
  • std::move is still used for efficiency, ensuring we don’t unnecessarily copy objects when setting or replacing values.
  • Like Rust’s Cell<T>, this isn’t thread-safe—use it in single-threaded scenarios or add synchronization if needed.
This version should feel very close to Rust’s Cell<T> in terms of behavior.

Date: 2025-03-01 22:51 (UTC)
ufm: (Default)
From: [personal profile] ufm
Я очень давно не настоящий сварщик в сях, но разве оно не должно быть что-то типа
Cell ? Т.е. сonst у нас не сама cell, а её содержимое.

Date: 2025-03-02 01:00 (UTC)
From: [personal profile] dedekha
Mutable это не одно и то же - cell cчитает references и не разрешает одновременно mutable и immutable.
OSZAR »