Что такое Result<T, E>
2025-02-27 13:31Есть в Rust популярная идиома: Result<(), String>. Используется для функций, которые в случае успехи ничего не возвращают, а в случае ошибки возвращают текстовую строку. К примеру, копирование одного файла в другой из реального проекта:
Question: There is a popular idiom: Result<(), String>. I would like to see how it could be implemented in C++.
Давайте попросим grok.com сделать аналогичный класс на Си++.fn copy_file_contents(mut dest_file: &fs::File, src_filename: &str) -> Result<(), String> {
let mut src_file = fs::File::open(src_filename)
.map_err(|e| format!("Failed to open source file '{}': {}", src_filename, e))?;
io::copy(&mut src_file, &mut dest_file)
.map_err(|e| format!("Failed to copy to destination: {}", e))?;
Ok(())
}
Question: There is a popular idiom: Result<(), String>. I would like to see how it could be implemented in C++.
Let’s dive into translating the Rust idiom Result<(), String> into C++. In Rust, Result is an enum used for error handling, with two variants: Ok(T) for success and Err(E) for failure. Here, Result<(), String> means a function returns either "success with no meaningful value" (Ok(()), where () is the unit type) or "failure with a string error message" (Err(String)). This is a common pattern in Rust for operations that either succeed without producing a value or fail with a descriptive error.А вот пример использования такого класса, тоже сгенерённый Гроком.
In C++, we don’t have a direct equivalent to Rust’s Result in the standard library, but we can approximate it using modern C++ features. The closest analogs are std::optional, std::variant, or a custom implementation.
Let’s define a reusable Result class with a Rust-like interface, using std::variant under the hood. This adds convenience methods like is_ok(), is_err(), unwrap(), and unwrap_err().#include <variant>This version is the most Rust-like. VoidResult directly corresponds to Result<(), String>. You can call is_ok() or is_err() to check the state, and unwrap() or unwrap_err() to extract values (with error checking). It’s reusable for other T and E types too.
#include <string>
#include <stdexcept>
#include <iostream>
template<typename T, typename E>
class Result {
public:
Result(T value) : data(value) {}
Result(E error) : data(error) {}
bool is_ok() const { return std::holds_alternative<T>(data); }
bool is_err() const { return std::holds_alternative<E>(data); }
T unwrap() const {
if (is_err()) throw std::runtime_error("Called unwrap() on an Err value");
return std::get<T>(data);
}
E unwrap_err() const {
if (is_ok()) throw std::runtime_error("Called unwrap_err() on an Ok value");
return std::get<E>(data);
}
private:
std::variant<T, E> data;
};
// Specialize for empty value and std::string
struct Void {};
using VoidResult = Result<Void, std::string>;
Вполне разумная вещь получилась. Можно использовать в разных Си++ проектах. Полный исходник лежит здесь: result.cppVoidResult do_something(bool fail = false) {
if (fail) {
return std::string("Something went wrong");
}
return Void{};
}
int main() {
// Test success case
auto success = do_something(false);
if (success.is_ok()) {
std::cout << "Success! Unwrapped: Void\n";
success.unwrap(); // Works fine, returns Void
}
// Test failure case
auto failure = do_something(true);
if (failure.is_err()) {
std::cout << "Error: " << failure.unwrap_err() << "\n";
}
// Test unwrap() on failure (will throw)
try {
failure.unwrap();
} catch (const std::runtime_error& e) {
std::cout << "Caught: " << e.what() << "\n";
}
// Test unwrap_err() on success (will throw)
try {
success.unwrap_err();
} catch (const std::runtime_error& e) {
std::cout << "Caught: " << e.what() << "\n";
}
return 0;
}

no subject
Date: 2025-02-27 21:40 (UTC)no subject
Date: 2025-02-27 21:47 (UTC)Hi Grok,
Please show me how to implement Rust's type Result<T, E> in Scala.
Let's implement a simplified version of Rust's Result<T, E> type in Scala. In Rust, Result is an enum used for error handling with two variants: Ok(T) for success and Err(E) for failure. In Scala, we can achieve similar functionality using a sealed trait and case classes. Here's how we can do it:
// Define the Result type as a sealed trait sealed trait Result[+T, +E] { // Basic methods similar to Rust's Result def isOk: Boolean def isErr: Boolean // Map over the success value def map[U](f: T => U): Result[U, E] = this match { case Ok(value) => Ok(f(value)) case Err(error) => Err(error) } // Map over the error value def mapErr[F](f: E => F): Result[T, F] = this match { case Ok(value) => Ok(value) case Err(error) => Err(f(error)) } // Get the value or a default def getOrElse[U >: T](default: => U): U = this match { case Ok(value) => value case Err(_) => default } } // Success case final case class Ok[+T, +E](value: T) extends Result[T, E] { def isOk: Boolean = true def isErr: Boolean = false } // Error case final case class Err[+T, +E](error: E) extends Result[T, E] { def isOk: Boolean = false def isErr: Boolean = true } // Example usage object ResultExample { def divide(a: Int, b: Int): Result[Int, String] = { if (b == 0) { Err("Division by zero") } else { Ok(a / b) } } def main(args: Array[String]): Unit = { val result1 = divide(10, 2) val result2 = divide(10, 0) // Pattern matching result1 match { case Ok(value) => println(s"Success: $value") // Prints: Success: 5 case Err(error) => println(s"Error: $error") } result2 match { case Ok(value) => println(s"Success: $value") case Err(error) => println(s"Error: $error") // Prints: Error: Division by zero } // Using map val mapped = result1.map(_ * 2) println(mapped.getOrElse(0)) // Prints: 10 // Using getOrElse println(result2.getOrElse(-1)) // Prints: -1 } }no subject
Date: 2025-02-27 22:17 (UTC)Not monadic yet, not applicative; does not grab the stacktrace, does not record the timestamp, but otherwise yes, pretty close.
Here's my version.
no subject
Date: 2025-02-27 22:34 (UTC)no subject
Date: 2025-02-27 23:02 (UTC)Да похоже, не он один.
Когда я вижу в расте и в катсах эти ихние Results, я вспоминаю, как его задвигал лет этак 15 назад.
Ну и чо, и пусть.
no subject
Date: 2025-02-27 23:41 (UTC)no subject
Date: 2025-03-01 17:18 (UTC)no subject
Date: 2025-03-01 21:54 (UTC)no subject
Date: 2025-03-02 01:01 (UTC)fn f(x: i32) -> Result<i32, &'static str> { Ok(x + 1) } fn g(_x: i32) -> Result<i32, &'static str> { Err("Oops") } fn h(x: i32) -> Result<i32, &'static str> { Ok(x * 2) } fn main() { let ok = Ok::<i32, &'static str>(42); let oops = Err::<i32, &'static str>("Oops"); // left identity assert_eq!(Ok(42).and_then(f), f(42)); assert_eq!(Ok(42).and_then(g), g(42)); // right identity assert_eq!(ok.and_then(Ok), ok); assert_eq!(oops.and_then(Ok), oops); // associativity assert_eq!( ok.and_then(f).and_then(g), ok.and_then(|y| f(y).and_then(g))); assert_eq!( ok.and_then(f).and_then(h), ok.and_then(|y| f(y).and_then(h))); assert_eq!( oops.and_then(f).and_then(g), oops.and_then(|y| f(y).and_then(g))); assert_eq!( oops.and_then(f).and_then(h), oops.and_then(|y| f(y).and_then(h))); }Понятна, что жопу исключаем. Но мы её и в скале исключаем. Или что вы имели в виду?no subject
Date: 2025-03-02 06:54 (UTC)and_then! Ну тогда да, монада. Спасибо. Чтобы апликативность была, нужноand_also. B z не имел в виду монадичности по параметру "ошибка", хотя, в принципе, можно было бы.Cпасибо за разъяснения!
no subject
Date: 2025-03-02 16:05 (UTC)Т.е., когда вы говорите, X - не монада, то для меня это звучит как "невозможно построить операции над типом X, удовлетворяющие монадическим законам".
Построить `and_also` над растовым Result можно, поэтому (с моей точки зрения, конечно) Result является аппликативным функтором.
Извиняюсь за недопонимание :)
no subject
Date: 2025-03-02 16:21 (UTC)implicit class powerString(s: String) { def containsIgnoreCase(other: String) = s.toUpperCase.contains (other.toUpperCase) def strictContainsIgnoreCase(t: String) = !t.isEmpty && containsIgnoreCase (t) def | (alt: String) = if (s.isEmpty) alt else s def js = { require(s != null, "Illegal value: null") val s1 = s.trim val src = if (s1.startsWith("'") && s1.endsWith("'")) s1.tail.dropRight(1) else s1 "'" + src.replaceAll("\\\\","\\\\\\\\").replaceAll("\'", "\\\\'") + "'" } def decodeHex: Array[Byte] = { // credits: Tomoya Yamamoto, https://gist.github.com/tmyymmt/3727124 s.replaceAll("[^0-9A-Fa-f]", "").sliding(2, 2).toArray.map(Integer.parseInt(_, 16).toByte) } def quote = { val r = "\"" + s.replaceAll("\\\"", "\\\\\"").replaceAll("\n", "\\n") + "\"" r } }no subject
Date: 2025-03-03 04:43 (UTC)trait Extract32 { fn get_i32(&self, defval: i32) -> i32; } impl Extract32 for Result<i32, &'static str> { fn get_i32(&self, defval: i32) -> i32 { match self { Ok(v) => *v, _ => defval, } } } // и потом используем: println!("{}", Ok(42).get_i32(-1));Мало того, можно расширять все структуры имплементирующие какой-то интерфейс дополнительными методами.
У вас, кстати, extension methods по старому. В третьей есть
extension (s: String)и вперёд!no subject
Date: 2025-03-03 10:58 (UTC)implicit class ExtraShit(Result[Int,String]) extends Extract32: def getInt(defVal: Int) = getOrElse defValНо да, с равенством будет фигня полная. Надо доопределять, как и hashCode. A implicit case class не бывает.
Extension - это в третьей скале, а у меня тут чота вторая. Надо будет проапгрейдить, давно собирался.
no subject
Date: 2025-03-03 04:50 (UTC)// в одном месте class A // в другом месте trait I { ... } // невозможный синтаксис, вообще в третьем месте // после которого класс A везде в нашем коде воспринимается как тип I // как если бы он его имплементировал изначально implementation of I for A { ... }Extension methods эту проблему не решают, т.к. это просто сахар: либо обёрточный класс с нужными методами, либо статические методы.
В Scala можно делать implicit conversions, но это именно conversions, а не начальный класс, поэтому многое будет падать, например, equality.
Ограничения JVM, я так понимаю.
no subject
Date: 2025-03-03 10:29 (UTC)scala> object Z: | implicit class ZZ(s: String): | def wrap: String = s"<<$s>>" | // defined object Z scala> import Z._ scala> val z = ZZ("hello") val z: Z.ZZ = Z$ZZ@12d44a2a scala> z.equals(ZZ("hello")) val res0: Boolean = false Не case class, и не делегирует равенство.