Advertisement
ChaosBeing

GDScript Rust Option type

Jul 1st, 2025 (edited)
429
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
GDScript 10.47 KB | Source Code | 0 0
  1. extends Resource
  2.  
  3. ##Functions similar to Rust's Option type, except I can't enforce the internal type as there is no
  4. ## assignment operation overriding in GDScript. Instead use the assign method.
  5. ##Option exists only to wrap a value. If no value is available, wrap null.
  6. ##Mostly useful for wrapping Vectors (and to a lesser extent Arrays) as they cannot be null.
  7. ##Typically Options should be created with either the None or Some static functions.
  8. class_name Option
  9.  
  10.  
  11. ##The internal value we're wrapping, or null.
  12. var _value
  13.  
  14. ##The type of values this Option is allowed to store.
  15. ##This stores the script ('class') of the Object we wrap.
  16. var _script_type: Variant = null
  17.  
  18. ##The same as _script_type, but for built-in types (anything in the Variant.Type enum other than Object).
  19. var _builtin_type: Variant.Type = TYPE_MAX
  20.  
  21. ##The same as _builtin_type, but for built-in classes rather than types.
  22. ##This would be something like InputEvent, for example.
  23. var _class: String = ""
  24.  
  25.  
  26.  
  27. ##Option exists only to wrap a value. If no value is available, wrap null.
  28. ##The parameters are the value we wrap and, optionally, a class to restrict this Option's value to.
  29. ##If this optional parameter is given, only null and values of that type can be assigned to this Option.
  30. ##Otherwise this Option can wrap any value.
  31. func _init(value_or_null: Variant, force_type: Variant = null):
  32.     _value = value_or_null
  33.    
  34.     #No sense in trying to figure out what type this is if it's null.
  35.     if force_type == null:
  36.         return
  37.    
  38.     if force_type is Variant.Type:
  39.         _builtin_type = force_type
  40.    
  41.     #If this isn't in the Variant.Type enum, are we a built-in class?
  42.     elif force_type is String:
  43.         _class = force_type
  44.    
  45.     #If this isn't in the Variant.Type enum, and we aren't a built-in class, are we a script class?
  46.     elif force_type.has_method("get_global_name"):
  47.         _script_type = force_type
  48.    
  49.     else:
  50.         assert(false, "Encountered unexpected type at Option creation time!")
  51.  
  52.  
  53. ##Creates and returns an empty Option. If provided with a type, this Option will only allow
  54. ## data of the given type to be stored inside it.
  55. static func None(require_type = null) -> Option:
  56.     return Option.new(null, require_type)
  57.  
  58.  
  59. ##Creates and returns an Option wrapping the provided value, asserting that the value is not null.
  60. ##Only values of the same type as the data we're wrapping can be assigned to this Option in the future.
  61. static func Some(value_not_null: Variant) -> Option:
  62.     assert(value_not_null != null, "Attempted to create an Option by wrapping \"null\" in Some. Some cannot contain null!")
  63.     if value_not_null is Option:
  64.         push_warning("Wrapping type \"Option\" in an Option! " +
  65.             "Wrapping an Option inside of another Option is technically allowed, but likely unintentional.")
  66.    
  67.     var val_type = typeof(value_not_null)
  68.    
  69.     if val_type == TYPE_OBJECT:
  70.         if value_not_null.get_script() != null:
  71.             val_type = value_not_null.get_script()
  72.         else:
  73.             val_type = value_not_null.get_class()
  74.    
  75.     return Option.new(value_not_null, val_type)
  76.  
  77.  
  78.  
  79. ##Returns true if our internal value is null, false otherwise.
  80. func is_none() -> bool:
  81.     return _value == null
  82.  
  83. ##Returns true if our internal value is not null, false otherwise.
  84. func is_some() -> bool:
  85.     return _value != null
  86.  
  87. ##Only returns true if this Option is not None and the provided Callable returns true.
  88. ##The Callable must be able to take this Option's value as a parameter and return a boolean.
  89. ##For example, you could use is_some_and to check that a number A) exists and B) is even like so:
  90. ##var op_num = Some(16)
  91. ##op_num.is_some_and(func(num): return num % 2)
  92. ##This would return true, as the number does exist and is even.
  93. func is_some_and(fn: Callable) -> bool:
  94.     if is_none():
  95.         return false
  96.    
  97.     var result = fn.call(self.unwrap())
  98.     assert(result is bool, "The Callable passed to is_some_and must return a bool!")
  99.     return result
  100.  
  101.  
  102.  
  103. ##Returns our internal value and asserts it is not null (a None Option).
  104. func unwrap():
  105.     assert(_value != null, "Attempted to unwrap a null value.")
  106.     return _value
  107.  
  108. ##If our internal value is not null, return it.
  109. ##Otherwise, we call the provided callable and return its result.
  110. ##We assert that the value returned by the callable is not null.
  111. func unwrap_or_else(callable: Callable):
  112.     if _value == null:
  113.         var result = callable.call()
  114.         assert(result != null, "Callable passed to unwrap_or returned null!")
  115.         return result
  116.     return _value
  117.  
  118. ##If our internal value is not null, return it. Otherwise, return the provided default.
  119. ##We assert that default is not null.
  120. func unwrap_or_default(default):
  121.     if _value == null:
  122.         assert(default != null, "Default value provided to unwrap_or_default was null!")
  123.         return default
  124.     return _value
  125.  
  126. ##The same as unwrap, but if we are a None Option (our internal value is null)
  127. ## the provided string is printed by the assert statement instead of a generic error message.
  128. func expect(err_msg: String) -> Variant:
  129.     assert(_value != null, err_msg)
  130.     return _value
  131.  
  132.  
  133.  
  134. ##If our internal value is null, we return a new None.
  135. ##Otherwise we call the provided Callable, passing our internal value as an argument
  136. ## and returning the result wrapped in Some.
  137. ##map differs from and_then in that map is meant to be infallible, it returns None only if
  138. ## this Option itself is None. In other words, it can take a Callable returning any value.
  139. ##map exists mostly to extract information from an Option, if it exists.
  140. ##and_then is primarily used to perform some fallible operation on the condition a value exists.
  141. ##and_then only takes Callables that return either an Option or null.
  142. func map(callable: Callable) -> Option:
  143.     if is_none():
  144.         return None()
  145.    
  146.     var result = callable.call(_value)
  147.     if result == null:
  148.         return None()
  149.    
  150.     if result is Option:
  151.         push_warning("Value returned by Callable provided to map() returns type Option. " +
  152.             "This will result in an Option wrapped inside another Option. Is this intended?")
  153.    
  154.     return Some(result)
  155.  
  156.  
  157. ##If our internal value is null, we return the default value.
  158. ##Otherwise we call the provided Callable, passing our internal value as an argument
  159. ## and returning the result as long as it isn't null.
  160. ##If the Callable does return null, we return the default value.
  161. ##map_or differs from map in that it always returns a value while map returns an Option.
  162. ##Basically, map_or is the same as if you called map and then used unwrap_or on the result.
  163. func map_or(default: Variant, callable: Callable) -> Variant:
  164.     assert(default != null, "Null cannot be passed as the default value to map_or!")
  165.     if default is Option:
  166.         push_warning("Default value provided to map_or is of type Option. " +
  167.                 "Would map() or and_then() work better?")
  168.    
  169.     if is_none():
  170.         return default
  171.    
  172.     var result = callable.call(_value)
  173.     if result != null:
  174.         if result is Option:
  175.             push_warning("Value returned by Callable in map_or is of type Option. " +
  176.                 "Would map() or and_then() work better?")
  177.         return result
  178.    
  179.     return default
  180.  
  181.  
  182. ##If our internal value is null, we return a new None.
  183. ##Otherwise we call the provided Callable, passing our internal value as an argument
  184. ## and returning the result. If the result is null, we return None.
  185. ##and_then differs from map in that and_then is primarily used to perform operations on the
  186. ## condition a value exists. These operations may be fallible, and so the Callable must
  187. ## return an Option or null.
  188. ##map exists mostly to extract information from an Option, if it isn't None,
  189. ## and always returns the extracted information wrapped in a new Option.
  190. func and_then(callable: Callable) -> Option:
  191.     if is_none():
  192.         return None()
  193.    
  194.     var result = callable.call(_value)
  195.     if result == null:
  196.         return None()
  197.    
  198.     assert(result is Option, "Callable passed to and_then must return an Option! Would map_or be better?")
  199.     return result
  200.  
  201.  
  202.  
  203. ##Returns true if the provided value is the same as our internal value.
  204. ##Always returns false if our internal value is null.
  205. func matches(comp: Variant) -> bool:
  206.     if _value == null:
  207.         return false
  208.     return comp == _value
  209.  
  210.  
  211. ##Assigns a new value to this Option. If not null, and we've restricted what type of data
  212. ## this Option can accept, we assert that the new value is the correct type.
  213. func assign(new_value: Variant):
  214.     if new_value != null:
  215.         #Make sure we have some sort of restrictions before asserting the new value matches them.
  216.         if get_type() != null:
  217.             assert(is_same_type(new_value), "Attempted to assign value \"" + str(new_value) +
  218.                 "\" to Option that only wraps type \"" + get_type_name() + "\".")
  219.     _value = new_value
  220.  
  221.  
  222.  
  223. ##Returns the class of data that this Option is allowed to store.
  224. ##If null is returned, any value is allowed.
  225. func get_type() -> Variant:
  226.     if _builtin_type != TYPE_MAX:
  227.         return _builtin_type
  228.     if !_class.is_empty():
  229.         return _class
  230.     return _script_type
  231.  
  232. ##Returns the name of the type of data this Option is allowed to store.
  233. ##If no type is set, the String "NO TYPE SET" is returned.
  234. func get_type_name() -> String:
  235.     var result: String = "NO TYPE SET"
  236.     if _builtin_type != TYPE_MAX:
  237.         result = type_string(_builtin_type)
  238.     elif _script_type != null:
  239.         result = str(_script_type.get_global_name())
  240.     elif !_class.is_empty():
  241.         result = _class
  242.     return result
  243.  
  244.  
  245.  
  246. ##Returns true if this Option's value is of the specified type or inherits from it.
  247. ##If this Option is None, returns false.
  248. ##Mostly useful in asserts to guarantee types passed to functions.
  249. func is_type_not_none(check_type) -> bool:
  250.     assert(check_type != null, "Passed \"null\" to is_type_not_none - this is not allowed!" +
  251.         "A type must be provided. Did you want to use is_none()?")
  252.    
  253.     return is_instance_of(_value, check_type)
  254.  
  255.  
  256. ##Returns true if this Option's value is of the specified type or None.
  257. ##Mostly useful in asserts to guarantee types passed to functions.
  258. func is_type_or_none(check_type) -> bool:
  259.     return _value == null || is_type_not_none(check_type)
  260.  
  261.  
  262. ##Does the given value have the same type as this Option?
  263. ##Always returns false if the given value is null.
  264. func is_same_type(check_value) -> bool:
  265.     if check_value == null:
  266.         return false
  267.    
  268.     if _builtin_type != TYPE_MAX:
  269.         return is_instance_of(check_value, _builtin_type)
  270.    
  271.     if _script_type != null:
  272.         return is_instance_of(check_value, _script_type)
  273.    
  274.     if !_class.is_empty():
  275.         return check_value.is_class(_class)
  276.         #return check_value.get_class().match(_class)
  277.    
  278.     #If we made it to this point... What happened!?
  279.     assert(false, "Option is somehow both None and not None in is_same_type.")
  280.     return false
  281.  
Advertisement
Comments
  • ChaosBeing
    5 hours (edited)
    # text 0.59 KB | 0 0
    1. I made this while learning GDScript in a beta version of Godot 4.3. As such there may be more optimal solutions to some problems, either that I had overlooked or easier ways to tackle issues in newer versions. (I remember dealing with types in particular was very cumbersome.)
    2. I stayed as true to the Rust standard library as I could, but I did add "is_type_not_none" and "is_type_or_none" for convenience sake.
    3. This wound up working surprisingly well, though this is still Godot and trying to cram it into places it doesn't belong will only result in headaches. (I'm looking at you, exported Resources!)
Add Comment
Please, Sign In to add comment
Advertisement